The repository pattern is an essential part of object-oriented software development. It has become the most popular abstraction to retrieve data from the database and map them into business objects (Aggregates).
Lately I have noticed that many developers choose to restrict their repository to follow a CRUD (Create, Read, Update, and Delete) pattern, where the methods' names are generic and contain a verb associated with CRUD such as get or delete. The methods are stay highly versatile by accepting a object parameter with many properties so the consumer ca re-use the method in many different scenarios.
Example of a User repository interface (typescript):
export interface IUserRepository {
getUser({ id?: number, email?: string, role?: string }): Promise<User>
}
This pattern seem to be loosely inspired by Eric Evan's description of a specification-based repository.
A SPECIFICATION is a way of allowing a client to describe (specify) what it wants without concern for how it
will be obtained in the process creating an object that can actually carry out the selection
The issue comes when constraining repositories to only follow a specification / CRUD interface. Methods suddenly have to cover many cases and they become bloated. Additionally, the method names do not convey the same intent as compared to "hard-coded" methods
await playerRepository.getPlayer({ location: 'USA', highScore: 500 })
await playerRepository.getPlayerFromUSAWithHighScore(500)
In the example above, we can see the two different approaches. The "Specification-Based" does not convey the intent very well. It is not obvious if it is retrieving a Player from the USA and with a high score of 500, or if it is retrieving a Player from the USA or with a high score of 500.
Additionally, Eric Evans pointed out that we should allow for repositories to contain hardcoded methods, so as to maintain clarity in the domain design.
Even a REPOSITORY design with flexible queries should allow for the addition of specialized hard-coded
queries. These might be convenience methods that encapsulate an often-used query, or a non-object query
such as mathematical summaries of selected objects. Frameworks that don’t allow for such contingencies
tend to distort the domain design or get bypassed by developers.
Having said that, it is important for hardcoded methods not to encapsulate the definition of domain terms. For example:
interface IArticleRepository {
getMostPopularArticles(limit: number)
}
In this example, we are defining what a "Popular" article is within the implementation of a repository, which should belong as an infrastructure concern. We are violating the segregation of concern.
Finally, I would like to point out that the specification-based example given here do not follow Eric Evan's example of what a specification-based query should look like. It just happens to be the most common repository structure I come across.
If you're curious, this is an example of a specification-based repository query given by Eric Evans in his book:
const criteria = new Criteria();
criteria.equal(TradeOrder.SECURITY, "WCOM");
criteria.equal(TradeOrder.ACCOUNT, "123");
const order = tradeOrderRepository.matching(criteria);
Top comments (0)