Chapter 7. Repositories

A repository is a well-known global interface that encapsulates retrieval and search behavior emulating a collection of objects. We want to decouple the client code from the persistence layer: the respository works like a façade to the persistance infrastructure and the client itself.

There are two mainstream strategies for repository’s implementation, each with pros and cons: generic repositories and custom ones.

Generic repositories

Generic repositories follow the path tracked from O/RM tools like Hibernate, Entity Framework and so on. They often expose commands like Save<T>(T), Remove<T>(T) and queries like Select<T>(AbstractQueryObject) and GetFromIdentity<T>(long), providing a consistent API for both storage and retrieving. Such APIs are often easy to implement with any well known O/RM out of there (once you stoop to compromises with the tool itself). However, away back in 2009, Greg Young noted that the approach does not add much value for the application itself, while it causes further issues.

To my money, the approach suffer of both implicitness and over-abstraction: out-of-bounds comunication are needed for business rule such as "a customer can not be deleted" or "you can search bonds by rating". Clients are coupled with implementation details behind the scenes, so that they can be changed indipendently without recompilation but they break at runtime.

Custom repositories

Custom repositories provide specialized interfaces for each aggregate root with methods like GetCustomer(VatCode) and GetBonds(Rating) : they are explicit contracts and that the clients will have to know. They can expose commands tailored to the aggregate root, minimizing the need for out-of-bounds comunications.

If Epic was written in Java, this would be the preferred approach.

Enter Linq

Language Integrated Query (or Monads, as you like) has been one of the reason that made us to choose C# for modeling. It provides a generic, strongly typed and language integrated method that gives us the best of both approaches: we can derive IQueryable<T> adding specific queries that can not be expressed in term of object properties, but we can still search in a generic way without coupling client the implementation.

Indeed, the base interface for Epic repositories is apparently simple:

As you can see in the sample code, Epic does not require that repositories implement such interface (IQueryable<TEntity> could be enough for the client), but IRepository<TEntity, TIdentity> expresses the vision we have on the subject:

  • only entities can be aggregate roots;
  • no base interface is required to connect the entity and its own identifier;
  • no command method should be exposed by the repository;
  • all repositories should be queryable.

Commands as implementation details

Most of repositories you will encounter out of there, provide methods as Add(Entity) or Delete(Entity).

In Epic based applications, instead, no repository should provide such methods, as they are always owned by a bounded role. Indeed there is always a user responsible for the creation or the elimination of an entity.

The persistence of a modified entity, however, should not correspond to any command at all: entities should be updated on the database (or wherever they are) automatically, thanks to the repository that observes exposed events.

In Epic.Linq you will find the repository’s base class, that will provide methods to add and remove new entities from the identity map and tools to observe the changes. However, such methods will be designed for the implementation of the bounded roles, only.

Explicit, explicit, explicit

If you choose to use the IRepository<TEntity, TIdentity> interface, do not forget to derive your own: even if you do not add any query method to the contract, it is always better to define a specialized interface to maximize the forward compatibility of the code.

This will make the client code more explicitly coupled with the repository itself and you will be able to add query methods to the repository without issues related to assembly’s versioning.