The Curious Dev

The Repository Design Pattern

The repository interface belongs to the domain. The implementation, on the other, goes in the infrastructure layer

On a recent project, I was forced to leave the traditional Data Access Object (DAO) behind and apply the Repository design pattern instead. One of the things I like most about this approach is it makes your code more cohesive and it keeps layers loosely coupled together.

The Repository provides a way to treat objects as an in-memory collection of items. It’s an abstraction over the underlying data persistence mechanism and it enables us to query, add or delete our object through an interface. The advantage here is that we define this interface at the domain level. This is very important to note. Remember the infrastructure layer and the user interface layer all depend on the domain layer. The domain layer is the reason your application exists in the first place. It’s your business logic.

The infrastructure level is where you provide the implementation of the Repository interfaces you defined in your domain. By doing this, your code has already satisfied two Object Oriented design principles: programming to interfaces and separation of concerns. Just think about it.

Alright. Let’s look at some code. Say we want to provide a way for users to upload items (any item doesnt matter). Say our item has a title, date posted and who posted it and any other field. Doesn’t matter. We can then have an ItemRepository that will define the interface through which an item is added, retrieved or removed from our catalog.

public interface ItemRepository {
   String nextAvailableId();

   void addItem(Item item);

   Optional<Item> findById(String itemId);

   void deleteItem(String itemId);
}

The code above is fairly straightforward. I’m including the method, nextAvailableId(), in the repository because each item has to have a unique id. To avoid duplicate keys, I think it’s best if the repository is responsible for generating the IDs. In addition, I like to make the keys URL safe as it could be used by a web or mobile client when making a GET request. For example:

curl https://api.yourdomain.com/v1/item/xhsikdsUDHYGSas7hasXmkj

The ItemRepository interface can then be implemented against the storage solution of your choice. You could have an implementation for Amazon’s DynamoDB, Google’s Cloud Datastore, MongoDB, MySQL etc. Though this isn’t practical and it isn’t the point of using a Repository as most people think, it certainly can be done.

The client for the ItemRepository can either be a service class or a resource class. For example, a UserService interface could hold the logic needed to upload an item. I prefer this approach because your resources will depend on the UserService and there will be no business logic leak into the higher-level presentation layer. If we wanted to add a GUI client in the future, nothing has to change in the domain layer. Our JavaFX or Swing controllers/presenters will only need to depend on our UserService interface and we can hook it up to an implementation either manually or via a dependency injection framework like Guice. Here’s an example.

public interface UserService {
    Item uploadNewItem(UploadData data);
}
The interface could then be implemented like this:
public class UserServiceImpl {
    @Inject
    private ItemRepository itemRepository;

    @Inject
    public UserServiceImpl(@Named("Google Cloud Datastore")
                            ItemRepository itemRepository);

    public Item uploadNewItem(UploadData data) {
        // validate data
        String urlSafeId = itemRepository.nextAvailableId();
        Item item = new Item();
        // call setters
        itemRepository.addItem(item);
        return item;
    }
}
Notice that the UserServiceImpl class only relies on the ItemRepository interface. This means our classes are loosely coupled together. This is actually an application of the Dependency Inversion Principle which states that “high level modules should not depend on low level modules. Both should depend upon abstractions” (wikipedia).

In the coming posts, we’ll see how the repository can easily get bloated with too many find methods and how to fix this by implementing a Query object and a Criteria API f