1. Overview

Often, the implementations of repository and DAO are considered interchangeable, especially in data-centric apps. This creates confusion about their differences.

In this article, we’ll discuss the differences between DAO and Repository patterns.

2. DAO Pattern

The Data Access Object Pattern, aka DAO Pattern, is an abstraction of data persistence and is considered closer to the underlying storage, which is often table-centric.

Therefore, in many cases, our DAOs match database tables, allowing a more straightforward way to send/retrieve data from storage, hiding the ugly queries.

Let’s examine a simple implementation of the DAO pattern.

2.1. User

First, let’s create a basic User domain class:

public class User {
    private Long id;
    private String userName;
    private String firstName;
    private String email;

    // getters and setters
}

2.2. UserDao

Then, we’ll create the UserDao interface that provides simple CRUD operations for the User domain:

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

2.3. UserDaoImpl

Last, we’ll create the UserDaoImpl class that implements the UserDao interface:

public class UserDaoImpl implements UserDao {
    private final EntityManager entityManager;
    
    @Override
    public void create(User user) {
        entityManager.persist(user);
    }

    @Override
    public User read(long id) {
        return entityManager.find(User.class, id);
    }

    // ...
}

Here, for simplicity, we’ve used the JPA EntityManager interface to interact with underlying storage and provide a data access mechanism for the User domain.

3. Repository Pattern

As per Eric Evans’ book Domain-Driven Design, the “repository is a mechanism for encapsulating storage, retrieval, and search behavior, which emulates a collection of objects.”

Likewise, according to Patterns of Enterprise Application Architecture, it “mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.”

In other words, a repository also deals with data and hides queries similar to DAO. However, it sits at a higher level, closer to the business logic of an app.

Consequently, a repository can use a DAO to fetch data from the database and populate a domain object. Or, it can prepare the data from a domain object and send it to a storage system using a DAO for persistence.

Let’s examine a simple implementation of the Repository pattern for the User domain.

3.1. UserRepository

First, let’s create the UserRepository interface:

public interface UserRepository {
    User get(Long id);
    void add(User user);
    void update(User user);
    void remove(User user);
}

Here, we’ve added a few common methods like get, add, update, and remove to work with the collection of objects.

3.2. UserRepositoryImpl

Then, we’ll create the UserRepositoryImpl class providing an implementation of the UserRepository interface:

public class UserRepositoryImpl implements UserRepository {
    private UserDaoImpl userDaoImpl;
    
    @Override
    public User get(Long id) {
        User user = userDaoImpl.read(id);
        return user;
    }

    @Override
    public void add(User user) {
        userDaoImpl.create(user);
    }

    // ...
}

Here, we’ve used the UserDaoImpl to send/retrieve data from the database.

So far, we can say that the implementations of DAO and repository look very similar because the User class is an anemic domain. And, a repository is just another layer over the data-access layer (DAO).

However, DAO seems a perfect candidate to access the data, and a repository is an ideal way to implement a business use-case.

4. Repository Pattern With Multiple DAOs

To clearly understand the last statement, let’s enhance our User domain to handle a business use-case.

Imagine we want to prepare a social media profile of a user by aggregating his Twitter tweets, Facebook posts, and more.

4.1. Tweet

First, we’ll create the Tweet class with a few properties that hold the tweet information:

public class Tweet {
    private String email;
    private String tweetText;    
    private Date dateCreated;

    // getters and setters
}

4.2. TweetDao and TweetDaoImpl

Then, similar to the UserDao, we’ll create the TweetDao interface that allows fetching tweets:

public interface TweetDao {
    List<Tweet> fetchTweets(String email);    
}

Likewise, we’ll create the TweetDaoImpl class that provides the implementation of the fetchTweets method:

public class TweetDaoImpl implements TweetDao {
    @Override
    public List<Tweet> fetchTweets(String email) {
        List<Tweet> tweets = new ArrayList<Tweet>();
        
        //call Twitter API and prepare Tweet object
        
        return tweets;
    }
}

Here, we’ll call Twitter APIs to fetch all tweets by a user using his email.

So, in this case, a DAO provides a data access mechanism using third-party APIs.

4.3. Enhance User Domain

Last, let’s create the UserSocialMedia subclass of our User class to keep a list of the Tweet objects:

public class UserSocialMedia extends User {
    private List<Tweet> tweets;

    // getters and setters
}

Here, our UserSocialMedia class is a complex domain containing the properties of the User domain too.

4.4. UserRepositoryImpl

Now, we’ll upgrade our UserRepositoryImpl class to provide a User domain object along with a list of tweets:

public class UserRepositoryImpl implements UserRepository {
    private UserDaoImpl userDaoImpl;
    private TweetDaoImpl tweetDaoImpl;
    
    @Override
    public User get(Long id) {
        UserSocialMedia user = (UserSocialMedia) userDaoImpl.read(id);
        
        List<Tweet> tweets = tweetDaoImpl.fetchTweets(user.getEmail());
        user.setTweets(tweets);
        
        return user;
    }
}

Here, the UserRepositoryImpl extracts user data using the UserDaoImpl and user’s tweets using the TweetDaoImpl.

Then, it aggregates both sets of information and provides a domain object of the UserSocialMedia class that is handy for our business use-case. Therefore, a repository relies on DAOs for accessing data from various sources.

Similarly, we can enhance our User domain to keep a list of Facebook posts.

5. Comparing the Two Patterns

Now that we’ve seen the nuances of the DAO and Repository patterns, let’s summarize their differences:

  • DAO is an abstraction of data persistence. However, a repository is an abstraction of a collection of objects
  • DAO is a lower-level concept, closer to the storage systems. However, Repository is a higher-level concept, closer to the Domain objects
  • DAO works as a data mapping/access layer, hiding ugly queries. However, a repository is a layer between domains and data access layers, hiding the complexity of collating data and preparing a domain object
  • DAO can’t be implemented using a repository. However, a repository can use a DAO for accessing underlying storage

Also, if we have an anemic domain, the repository will be just a DAO.

Additionally, the repository pattern encourages a domain-driven design, providing an easy understanding of the data structure for non-technical team members, too.

6. Conclusion

In this article, we explored differences between DAO and Repository patterns.

First, we examined a basic implementation of the DAO pattern. Then, we saw a similar implementation using the Repository pattern.

Last, we looked at a Repository utilizing multiple DAOs, enhancing the capabilities of a domain to solve a business use-case.

Therefore, we can conclude that the Repository pattern proves a better approach when an app moves from being data-centric to business-oriented.

As usual, all the code implementations are available over on GitHub.


» 下一篇: Keycloak 用户注册