In previous article, we have seen basics about implementing CRUD operations using EF core. Generally repository pattern is used to implement data access layers. In this article, we are going to have look at how to implement repository pattern.
What is it ?
Repository pattern is part of Domain Driven Design principals.
In the book – Patterns of Enterprise Application Architecture , the description of repository pattern by Martin Fowler:
A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory. Client objects declaratively build queries and send them to the repositories for answers. Conceptually, a repository encapsulates a set of objects stored in the database and operations that can be performed on them, providing a way that is closer to the persistence layer. Repositories, also, support the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.Martin Fowler
So, a repository is something which is in between business logic and the database (i.e. persistent layer). It also maintains set of domain objects (i.e. DbSet in case of EF core) in memory. All operations are performed on those in-memory collections. This help in reducing the chattiness with database servers. A repository also encapsulates and abstracts the internal logic of interacting with the database.
When I read this definition, my first question was – how it is different from traditional DAL classes ?
How is it different?
Traditionally, the data access layer classes interact with persistent layer directly. There is not In-Memory collection. This results in chattiness.
In case of repository pattern, repositories are supposed to hold all the objects In-Memory. All individual changes are applied to the in-memory collections. Then save method can be called to apply all those changes together to the persistent layer.
In my opinion, general goals for implementing a repository pattern should be:
- Reduce code duplication, just for performing CRUD operations if same lines of code should not be required to be repeated.
- Increase code testability, implementing repository pattern should help testability of the solution. It should be easy to introduce stubs to ensure that repositories can be tested.
- Abstraction, the implementation should hide the details from repository consumers.
How to implement?
Repository pattern can be implemented using different approaches. Some of the approaches can be:
- Create one repository for each business entity
- Create one base interface for repository and then all repositories would implement the interface
- Create one base interface, implement it in an abstract class.
Some might also prefer returning IQueryable instead of returning the actual entities. Returning IQueryable may provide flexibility as the caller can then append additional criteria to further refine the interaction between data store and application. But returning IQueryable also has its own disadvantages. It might lead leaking of business logic in several layers. Returning actual data instead of IQueryable helps in encapsulating all the logic together in the repository and hence, we are going to follow that approach.
For today’s demo, we are going to create:
- a base repository interface, IBaseRepository, which accepts a type parameter, TEntity, which should be an entity
- an abstract base class, BaseRepository, which implements the base repository interface
Then for every entity,
- Create an interface, IEntityRepository (where Entity to be replaced by actual entity name), which declares essential methods for implementing the repository. This interface should derive from the base repository interface
- Create a class, EntityRepository, which extends BaseRepository and implements IEntityRepository.
Below code shows a base repository interface with some common methods. Note that the pattern does not tell which methods should be present in the base interface. It totally depends on how application is using entities and which methods are common to almost all the entities.
Then the base interface is implemented by BaseRepository<TEntity>. If you need to know details about this implementation, refer my previous blog post.
Entity Specific Repositories
Assuming that all entities needs these common methods, implementing repositories is simple. Just create entity specific repository’s interfaces and implementation as shown in below snapshots. All the implementation is getting derived from the base repository implementation.
So, we have created repositories for every entity. This implementation is not complete yet. Base repository does not load related entities yet. E.g. posts repository just loads post information excluding associated categories, tags, comments etc. In coming posts, we will try to enhance this implementation to load related data.
Currently we are assuming that all repositories need same implementations. This might be the case initially when new application is being setup. But over the time, requirements evolve, and it may cause having more and more entity specific implementations. Thus over the time, the concrete repositories would add more methods.
Eventually, some applications may also add / remove methods from base repository (or a common base implementation can completely be removed if there are no common methods). But having a base repository certainly reduces some typing efforts during new project setup.
I hope you find this information useful. Let me know your thoughts.