I’ve been spending a lot of time putting TDD/DDD into practice lately, and obsessing over all sorts of small-yet-important details as I try concepts and patterns for myself.
One pattern that has recently become popular in mainstream .NET is IRepository<T>. Originally documented in PoEAA, a repository is an adapter that can read and write objects from the database as if it were an in-memory collection like an array. This is called persistence ignorance (PI), and lets you forget about underlying database/ORM semantics.
Somewhere along the line however, someone realized that with generics you can create a single, shared interface that will support pretty much any operation you could ever want to do with any sort of object. They usually look something like this:
public interface IRepository<T> { T GetById(int id); IEnumerable<T> GetAll(); IEnumerable<T> FindAll(IDictionary<string, object> propertyValuePairs); T FindOne(IDictionary<string, object> propertyValuePairs); T SaveOrUpdate(T entity); void Delete(T entity); }
If you need to add any custom behaviour, you can simply subclass it — e.g. IProjectRepository : IRepository — and add new methods there. That’s pretty handy for a quick forms-over-LINQ-to-SQL application.
However, I don’t believe this is satisfactory for applications using domain-driven design (DDD), as repositories can vary greatly in their capabilities. For example, some repositories will allow aggregates to be added, but not deleted (like legal records). Others might be completely read-only. Trying to shoehorn them all into a one-size-fits-all IRepository<T> interface will simple result in a lot of leaky abstractions: you could end up with a Remove() method that is available but always throws InvalidOperationException, or developer team rules like “do not never call Save() on RepositoryX”. That would be pretty bad, so what else can we do instead?
Possibility #2: no common repository interface at all
The first alternative is simply dropping the IRepository<T> base, and make a totally custom repository for each aggregate.
public interface IProjectRepository { Project GetById(int id); void Delete(Project entity); // ... etc }
This is good, because we won’t inherit a bunch of crap we don’t want, but similar repositories will have a lot of duplicate code. Making method names consistent is now left to the developer — we could end up with one repository having Load(), another Get(), another Retrieve() etc. This isn’t very good.
Thinking about it, a lot of repositories are going to fit into one or two broad categories that share a few common methods — those that support reading and writing, and those that only support reading. What if we extracted these categories out into semi-specialized base interfaces?
Possibility #3: IReadOnlyRepository and IMutableRepository
What if we provided base interfaces for common repository types like this:
This is better, but still doesn’t satisfy all needs. Providing a GetAll() method might be helpful for a small collection of aggregates that we enumerate over often, but it wouldn’t make so much sense for a database of a million customers. We still need to be able to include/exclude standard capabilities at a more granular level.
Possibility #4: Repository Traits
Let’s create a whole load of little fine-grained interfaces — one for each standard method a repository might have.
public interface ICanGetAll<T> { IEnumerable<T> GetAll(); } public interface ICanGetById<TEntity, TKey> { TEntity GetById(TKey id); } public interface ICanRemove<T> { void Remove(T entity); } public interface ICanSave<T> { void Save(T entity); } public interface ICanGetCount { int GetCount(); } // ...etc
I am calling these Repository Traits. When defining a repository interface for a particular aggregate, you can explicitly pick and choose which methods it should have, as well as adding your own custom ones:
public interface IProjectRepository : ICanSave<Project>, ICanRemove<Project>, ICanGetById<Project, int>, ICanGetByName<Project> { IEnumerable<Project> GetProjectsForUser(User user); }
This lets you define repositories that can do as little or as much as they need to, and no more. If you recognize a new trait that may be shared by several repositories — e.g., ICanDeleteAll — all you need to do is define and implement a new interface.
Side note: concrete repositories can still have generic bases
Out of interest, here’s what my concrete PersonRepository looks like:
There’s very little new code in it, because all of the common repository traits are already satisfied by a generic, one-size-fits-all NHibernateRepository base class (which must be completely hidden from external callers!). The IPersonRepository just defines which subset of its methods are available to the domain layer.