A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.
Use the Repository pattern to achieve one or more of the following objectives:
- You want to maximize the amount of code that can be tested with automation and to isolate the data layer to support unit testing.
- You access the data source from many locations and want to apply centrally managed, consistent access rules and logic.
- You want to implement and centralize a caching strategy for the data source.
- You want to improve the code’s maintainability and readability by separating business logic from data or service access logic.
- You want to use business entities that are strongly typed so that you can identify problems at compile time instead of at run time.
- You want to associate a behavior with the related data. For example, you want to calculate fields or enforce complex relationships or business rules between the data elements within an entity.
- You want to apply a domain model to simplify complex business logic.
- Do not use IQueryable<T> as the return type of query methods. Because it creates tight coupling and you get no benefit over using the OR/M directly. Use more generic types like IEnumerable<T>.
- Create repositories for domain aggregates only not for every single entity. If you are not using Aggregates in a Bounded Context, the repository pattern may be less useful.
- Creating base types for repositories is not recommended. If it is needed make sure not breaking the Interface Segregation Principle.
- It is not recommended to create generic repositories, make sure you have a good reason if you want to create one.
- Repositories must return domain entities which are usable in the domain services without having to be converted, so any data contracts must be converted to domain entities before the repository returns them.
- Apart from CRUD operations, repositories can also have identity generator, count methods or finder methods.
- A repository should only have the minimum required operations. It doesn’t even have to include all the four of CRUD operations. It can be as simple as a repository with only one operation.
- It is OK to have bulk methods for adding or removing multiple items to or from the repository.
- If you find that you must create many finder methods supporting use case optimal queries on multiple Repositories, it’s probably a code smell. First of all, this situation could be an indication that you’ve misjudged Aggregate boundaries and overlooked the opportunity to design one or more Aggregates of different types. However, if you encounter this situation and your analysis indicates that your Aggregate boundaries are well designed, this could point to the need to consider using CQRS.
- Do not design a Repository to provide access to parts that the Aggregate Root would not otherwise allow access to by way of navigation.
- Place the repository interface definition in the same Module (assembly) as the Aggregate type that it stores. The implementation class goes in a separate Module. The following image shows how the dependencies should be
- Generally speaking, there is a one-to-one relationship between an Aggregate type and a Repository.
- If the aggregate includes large members, for example, big lists of items, you can consider lazy loading.
- The only responsibility of a repository is data persistence, so no business (domain) logic should be added to a repository. The operations are CRUD only. This also applies to the name of the methods.