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