DTO, DAO and Repository Patterns
One of the core differences between a new programmer and a seasoned one is the proper implementation of patterns.
Whereas most can implement something that works, the experienced developer knows that as soon as he's done, not only will specifications change but also that the codebase will have to be maintained.
He will try much harder to write something that's cohesive, maintainable and extendable - even so in detriment of speed. The seasoned developer only cares about optimization when there's clearly the need to optimize.
Given that introduction, let's see where DTO, DAO and Repository Patterns fall.
DTO - Data Transfer Object (pattern)
As per the name, this is a data-only object. Remember that DTO is a pattern and, as such, it is implementation independent. You can have a DTO that consists of one or more POJOs (Plain Old Java Objects).
User {
Long id;
String name;
String password;
}
Location {
String country;
String city;
String street;
}
// Notice that the DTO provides only the information that is relevant to the
// client, in this case password, city and street were removed for privacy
// reasons. The logic is that the DTO passes only relevant data.
UserDTO {
Long id;
String name;
String country;
// standard getters and setters
}
DAO - Data Access Object (pattern)
You'll use DAOs to simplify interactions between your program and the database. As such, in most cases, DAOs match tables. This allows a more leaned way to save/retrieve data from storage. It will also definitely help if you want to change storage down the line.
public interface UserDao {
void create(User user);
User read(Long id);
void update(User user);
void delete(Long id);
}
public class UserDaoImpl implements UserDao {
@Override
public void create(User user) {
// save user to storage
}
@Override
public User read(long id) {
// read user from storage
}
// etc
}
Repository Pattern
A repository sits between DAOs and the program. It focuses on business-domain abstraction. It can be used to prepare data and sent it to a DAO for persistence, or it can retrieve data from one (or more) DAOs and send it to the program.
The Repository's job is to expose domain semantics - "give me active users" - instead of "execute this SQL".
If the DAO absolutely matches the required fields, the DAO and the Repository would be exactly the same, so we'll use an example that's a bit more complex. Imagine we want to get a User
with Log
of performed actions.
Also imagine we've defined a LogDao
and a LogDaoImpl
as per the examples above.
public interface UserRepository {
User findById(Long id);
Optional<User> findByEmail(String email);
List<User> findActiveUsers();
List<User> findRecentlyRegisteredUsers(LocalDate since);
void save(User user);
void remove(User user);
}
public class UserWithLog extends User {
private User user;
private Log log;
public UserWithLog(User user, Log log) {
this.user = user;
this.log = log;
}
}
public class UserRepositoryImpl implements UserRepository {
private UserDao userDao;
private LogsDao logsDao;
public UserRepositoryImpl(UserDao userDao, LogsDao logsDao) {
this.userDao = userDao;
this.logDao = logDao;
public Optional<UserWithLog> findUserWithLogs(Long userId) {
// If no user exists with the given ID, return an empty Optional
Optional<User> userOpt = userDAO.findById(userId);
if (userOpt.isEmpty()) {
return Optional.empty();
}
// Fetch the log entry (or entries) for this user from the Logs DAO
// (Assumes getLog() retrieves the relevant log data)
Log log = logDAO.getLog();
// Combine the retrieved user and log data into a domain object
// and wrap it in an Optional before returning
return Optional.of(new UserWithLog(userOpt.get(), log));
}
}
As you can see, the Repository is responsible for combining data from the User and Log sources to produce a domain-level object (UserWithLogs
) that's pertinent to the application's needs.
If you haven't already, I strongly recommend that you buy and read Clean Architecture: A Craftsman's Guide to Software Structure and Design by Robert C. Martin.
It's the kind of book you'll find yourself revisiting every few years.