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.

By the incredible Steve J

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 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.

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 Logs of performed actions.

Also imagine we've defined a LogsDao and a LogsDaoImpl as per the examples above.

public interface UserRepository {
    void create(User user);
    User read(Long id);
    void update(User user);
    void delete(Long id);
}

public class UserWithLogs extends User {
    private List<Logs> logs;
}

public class UserRepositoryImpl implements UserRepository {
    private UserDaoImpl userDaoImpl;
    private UserWithLogs userWithLogsImpl;
    
    @Override
    public UserWithLogs get(Long id) {
        UserWithLogs user = (UserWithLogs) userDaoImpl.read(id);
        Logs logs = logsDaoImpl.read(user.getId());
        
        user.setLogs(logs);
        return user;
    }
}

As you can see, the Repository is responsible for extracting data for both User and Logs and then providing an object that's pertinent to the program's use-case.


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.