The repository pattern is polarizing. Some developers swear you should always use it to abstract data access logic while others think it’s unnecessary if you’re using an ORM. So should you use it? My answer is Yes and No! If you’re applying CQRS and Vertical Slice Architecture you’ll likely want a repository to build up Aggregates. However, for a Query, you may want to just get the data you need rather than an entire aggregate (or collection of aggregates) to build a view model.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
As with many terms and concepts in the software industry, a repository can mean different things depending on your definition. I’m using the definition from Martin Fowler’s P of EAA Catalog definition.
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
The key to part for me of that definition is domain objects. Not data models, but domain objects.
CQRS & Vertical Slices
Often I’m using CQRS to separate pathways between writes (commands) and reads (queries) to my database. This allows the definition of two distinct paths that each can decide how they interact with the database, their dependencies, etc. This isn’t top-level architecture but just a decision you can make in various parts of your system.
As mentioned earlier, CQRS is a concept that is often confused. Check out my post CQRS Myths: 3 Most Common Misconceptions that should clear up any confusion.
What ultimately happens when you start focusing individually on a command or a query leads to organizing your code around features. A feature can be an individual command or query, or a collection of a few.
A Vertical Slice is a concept of taking everything related to a feature and organizing it together. As mentioned, this becomes a natural fit with CQRS. Ultimately a feature is a single use-case or a defined set of functionality within your system.
Vertical Slices are focused on features, not technical concerns. No longer are you organizing and writing code in a layered approach. The layers and technical separation are defined per feature.
This means you can define how each command or query handles various concerns, for example, data access.
If you go back to the definition of the Repository Pattern, it’s for accessing domain objects. Domain Objects that are grouped together are defined as an Aggregate. To interact with the Aggregate, all operations are handled by a primary domain object which is the Aggregate Root.
The common example often used is a Sales Order and all the Line Items. The Sales Order and Line Items are domain objects that form an Aggregate. The Sales Order is the Aggregate Root. All operations are done through the Sales Order and no access is done directly to any Line Items. Check out my post on Aggregate Design: Using Invariants as a Guide for more on how to define and design an aggregate.
This means that I’m only concerned with an aggregate for making state changes. In other words, an Aggregate is required for Commands, not Queries.
This means that we can define to use an Aggregate for any Commands, and simply use a Data Model for any Queries. We do not need an Aggregate for queries because our Aggregate is responsible for state changes.
Also, most of the time when creating a Query, you want data to be shaped a certain way. This doesn’t necessarily require everything within an Aggregate. Because of this, you’re often way over fetching data to build the Aggregate when you only need a subset of the data for the Query.
To illustrate this, here is code from the eShopOnWeb sample application. The Order entity is the Aggregate Root that is returned from a Repository.
The sample code has a Query that is using the IOrderRepository to list all the Orders for the logged-in user.
Since we don’t need the Orders Aggregate, we don’t really need to use the Repository Pattern. The benefit of not using the repository is rather we can select the data we actually need for this use case. In this sample, it was reusing the OrderViewModel to be used when listing all the Orders as well in another route when viewing an individual Order.
This re-use is actually not helpful because the Order Listing page does not need any of the Order Items or the Shipping Address.
Rather, we can define our result explicitly for this use case and fetch exactly the data needed. Again, this use case did not need any order items, the product for those order items, or the Shipping Address. The aggregate is fetching and returning all this data that we do not need.
The Repository Pattern
If I’m applying CQRS and Vertical Slices, it means that on the Command side I’m going to use a Repository to build up and return an Aggregate. An aggregate is a consistency boundary and is responsible for state changes that are controlled by invariants.
On the Query side, since I’m not making any state changes, I do not need an Aggregate. An aggregate is likely way more data that I likely need to transform into the result that I need to create. Queries are specific use cases in ways to return data. A Query is encapsulating that concept including how it’s accessing that data. You could decide to not even use the same library for underlying data access in your Repository as you are in any Queries. CQRS enables that option.
Developer-level members of my YouTube channel or Patreon get access to the full source for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.
- CQRS Myths: 3 Most Common Misconceptions
- Aggregate Design: Using Invariants as a Guide
- Aggregate (Root) Design: Behavior & Data