Pair Programming: It Takes Two to Tango
Andrea Alilović Software Engineer
Publish date 24.11.2021.
Reading time 6 min
Autor Damir Bolic
Position Software Engineer
Category Development
CQRS (Command Query Responsibility Segregation) pattern is a simple pattern but is often misunderstood. At least for me, this pattern was quite scary at the beginning, while in fact, it’s quite simple and useful. In this guide, I will explain what it is, how it works, and implement demo project using CQRS/DDD principles. For this, I will be using .NET Core stack, MSSQL database. Demo code is available on my github (https://github.com/bolicd/practicalcqrs) and should be properly documented to get you started.
CQRS is an architectural pattern that separates read and write operations. This allows the application to scale better and perform well under a heavy load. Fundamentally, it is an implementation of the single responsibility principle (SRP) applied to the domain model layer.
It is useful for solving the issue of impedance mismatch — misalignment of database model and domain model.
There are no frameworks or tools that should be used, CQRS simply states that the domain write model and read model should be separated.
A simple diagram that shows the most important aspects of the CQRS:
Application service handles commands or query depending on the write or read side. For write, we use a command that returns no result. For read, we use a query that has no side effects and returns a result:
CAP theorem states that, for a distributed data store, it is impossible to achieve more than two of the following:
Depending on what needs to be scaled in CQRS system, Consistency or Availability can be sacrificed (or both) to achieve better scalability.
It is important for both business users as well as developers/architects to understand that CQRS will have trade-offs that can affect user experience.
Scaling the read side means introducing eventual consistency. In practice, this is usually done by creating denormalized read models which are populated from the write side. This can be done using:
Pooling agent can give more control, and guarantees at most once delivery in order. However, it requires it to be single threaded service that runs on one machine at a time (due to the fact that it needs to read and send events in order). This means that it is less scalable.
Queue is usually implemented using some off the shelf queue but we have less control over events distribution and more
If amount of requests coming from clients is high, write side can be scaled using Message Queue — commands are sent to the queue and client get the immediate response back. There are couple of things that business user needs to be aware in this scenario:
doesn’t mean that the command is processed
Command should be validated as best it can before it is placed on the queue, to minimize the chance of error later on when processing.
This way we increase scalability at a loss of availability.
In general, when we have rich/complex business logic CQRS is a good pattern to use.
It can provide limitless scalability for both the read and write side but with some trade-offs, like event consistency, which need to be clearly communicated to the business user.
CQRS can be built without Event Sourcing, asynchronous messaging, or eventual consistency.
I’ve created a small project to demonstrate most of the basic principles laid out in this article: https://github.com/bolicd/practicalcqrs is built upon my already existing project for event store (found here: https://github.com/bolicd/eventstore) but also adds more DDD principles:
Example of CQRS separations for both read and write side.
Write side uses simple event store
Read side uses projection agents which pool event store and populate read models as needed. This introduces eventual consistency.
The purpose of this project was NOT clear architecture so it should not be considered as such. Mostly everything in this project is implemented by hand so that the source code can be easily understood (as opposed to using pre existing library for example).
Pooling agent is implemented as Projections, where each projection can react to certain events from the event store and populate the corresponding read model.
I do hope that this will help someone better understand DDD and apply them.