The Repository Pattern is a great way to extract the low level data access code from your business logic. This separation of concerns also eases the testability of a system. Repositories could either use web services, databases or even in-memory collections, all without your business layer even knowing about them. Persistence ignorance is an important aspect of Domain-Driven Design and the Repository Pattern will help you attain this premise.
A number of posts already exist explaining the Repository Pattern, but most stop at the concept of the generic repository (or IRepository<T>). In this example, we will explore concrete implementations and important aspects to consider when implementing the Repository Pattern.
1) Favour Composition Over Inheritance
So, you have created a base generic repository for an NHibernate concrete implementation (NHRepository<T>) and now you want your repositories to use it. The logic progression would be to extend your base repository (NHRepository<T>) along with your aggregate specific interfaces (i.e., IMovieRepository). Bad! By doing so, you are exposing all of the base repository’s details and behaviour to the subclass that you might not even need. Do you really need a Save? Do you really want to expose the IQueryable from the Find and risk queries being created in other layers of your application? A better approach would be to inject an IRepository<Movie> in your concrete repository and delegate the base repository calls to it. Doing it this way, you can also inject any types of IRepository<T>, decoupling the dependencies on the NHRepository<T>.
Here is an example of the first (bad) approach using inheritance:
And then here is a better approach using delegation:
You can see how the class MovieRepository doesn’t extend the NHRepository<Movie> anymore but instead is being injected by an IRepository<Movie>. Not only doesn’t it expose all of the parent’s details, but it can also be used with any types of repository.
2) Exposing the IQueryable (vs the IEnumerable)
Now, if you are using a specific data source like SQL Server along with an ORM tool, you can easily expose the LINQ IQueryable interface through your repository. This can help to optimize your queries when they are converted into SQL statements against the database. By using the IQueryable, you can further refine your query that will be executed in the database. Contrary to the IEnumerable, where you would need to load all the objects in memory and only then apply some filtering.
For example, if your generic repository exposes an IEnumerable and you wanted to return the top 10 items, your repository will need to first load all the items in memory and then take the top 10.
But, if your generic repository exposes the IQueryable, the expression will be translated to the appropriate SQL statement and load only 10 rows from the database into memory.
However, the IQueryable should be kept at the Repository layer only. Exposing IQueryable past your repository increases the possibility of an error occurring in other layers of your application. A developer could modify the query at the UI layer, retrieving entire tables from the database and bringing the entire system to a slow state. The "GetTop10" method in the above example should still return an IEnumerable to stop the IQueryable from bubbling up into the business and UI layers.
This post covered two simple concepts about the Repository Pattern which are often neglected when building the data/infrastructure layer. First, we covered why you should not extend you IRepository<T> but instead use delegation. Second we looked at the debate of exposing an IQueryable versus an IEnumerable and why the IQueryable is favorable when “shut” at the repository layer.