CQRS, Repository, and MediatR in .NET

Kostiantyn Bilous
SharpAssembly
Published in
4 min readDec 9, 2023

The principles of CQRS (Command and Query Responsibility Segregation), which separate CRUD (Create, Read, Update, Delete) operations into Command and Query, are considered an effective design practice. They imply differentiating actions over Entities: reading data (Read) and modifying data (Create, Update, Delete). The main idea of CQRS is that read operations should not change the state of the database. In contrast, data modification operations should not return data to the client, except for the ID of created Entities (with Create) or a flag of successful operation execution (with Update, Delete).

The ideal implementation of CQRS involves using separate databases for reading and writing data, followed by their synchronization. Adhering to CQRS principles and separating Command and Query in the code, even at the single database level, enhances data integrity and simplifies code maintenance.

The Repository pattern, which creates an abstract layer for executing CRUD queries, is critical to achieving CQRS design. The repository interface defines which actions can be performed on domain objects, indicating the expected result. Furthermore, a stricter interface limited to read methods can be defined to prevent unwanted changes in the database state. Notably, the correct implementation of the Repository interface should be independent of client logic, allowing changes in repository implementation without affecting the existing code as long as the original interface remains unchanged.

In the .NET environment, the MediatR library, which implements the Mediator pattern, is widely used. Its application facilitates the implementation of CQRS principles by creating separate Requests and corresponding RequestHandlers for each command and query. This isolates each call into a separate class, simplifying the logic in the calling class. Thus, the MediatR approach enriches the domain model in the DDD (Domain-Driven Design) architecture, expressing business logic through possible actions over domains.

A couple of weeks ago, I came across a blog discussion suggesting that MediatR commands should be considered part of the domain model, akin to Domain Events, and viewed as serializable method calls. Despite criticism that commands are processed at the application level and should belong to it, I tend to agree with the author that commands are part of the domain and describe permissible actions over it. Meanwhile, command handlers implemented at the application level should contain minimal business logic and act as orchestrators of command execution, with proper registration in the Dependency Injection (DI) container.

Practical Example

In the initial version of the InWestMan — a personal investment tracking application, the domain logic was inefficiently distributed: domain objects served only for data mapping through Entity Framework. In contrast, business logic was placed on domain services. This approach led to the "anemic" state of the domains, depriving them of any inherent logic, which I later realized was an antipattern known as the "Anemic Domain Model". The business logic was smeared across different application layers: constructors and validations with database calls were held in domain services, business logic in application services, and hardcoded queries in repository implementation. The hardcoded queries and handling requests for creating or modifying entities, which returned full objects, were particularly challenging. As a result, the code became a confusing "zoo" of approaches, making it extremely difficult to determine which object was the most relevant.

At the same time, introducing the MediatR library at work while developing new functionalities for a commercial product significantly improved the overall architecture and code cleanliness. Using MediatR commands to declare possible actions over domain objects and handlers at the application level for orchestrating calls to the database and other components led to a clear and understandable structure, explicitly defining actions that a client can perform over domains.

This new division significantly improved my understanding of CQRS and DDD, leading me to almost completely rewrite the InWestMan projects. To expand the application's functionality, I added a new command in the domain project and a corresponding method in the API controller. The command is created upon an API request and passed through a mediator for execution by a handler at the Application level, handling the technical side of the command. At the same time, the expansion of business logic occurs at the domain level. This approach allows me to develop new features without affecting old commands and their handlers. To add new functionality, I follow the vertical structure of "Controller" → "Command" → "Handler" → "Repository" → "Domain," striving to make each level as "thin" as possible and lowering the business logic as much as possible.

Conclusion

Applying CQRS principles in programming significantly improves the code structure and facilitates adaptation and expansion. However, it is essential to understand that successful implementation of CQRS requires careful architecture planning at the initial stages and strict discipline in its maintenance throughout the development cycle. This approach might not be the most suitable for projects with a limited lifespan, such as one-off solutions or prototypes. However, for developing business applications with a complex domain structure and the potential for scaling and further development, CQRS represents a valuable investment of effort and resources at the early stages of the project.

Subscribe for more:

https://sharpassembly.medium.com/
https://t.me/SharpAssembly

Credits: DALL·E generated

#DDD #CQRS #InWestMan

--

--

Kostiantyn Bilous
SharpAssembly

Senior Software Engineer (.Net/C#) at SimCorp, Ph.D. in Finance