Command Query Responsibility Segregation
This pattern gains popularity lately. And there’re certain benefits of using it as well as drawbacks. I would like to share my own experiences and thoughts on this topic.
ℹ️ Don’t forget to check out more advanced series dedicated to appliying CQRS architectural patter on top of HTTP [CQRS via HTTP].
The series covers in depth main actors of the pattern, such as Commanding and Querying, as well as how do they fit in the HTTP.
The Idea
CQRS stands for Command Query Responsibility Segregation. You might heart of it as CQS, which stands for Command-Query Separation.
As the name suggests, the essentials of the pattern lay in the separation (segregation) of writing (commanding) and reading (querying) the data. As long as the writing flow doesn’t query data and reading flow doesn’t mutate it, that is a CQRS. The main idea is that these two flows are independent and never cross each other.
What does independent mean, well it depends… It depends on where you apply the pattern. There are no certain rules to which part of the application/code/infrastructure it applies. CQRS could be applied to anything and probably you have already applied it somewhere without even realizing it. It could be the same class with different methods- one for checking incoming data, performing some calculations and saving results into a database, another method for building a query, retrieving and shaping the result. It could be different applications, one application for writing the data into a database, another for reading it from the same or projected database. It could be a set of serverless functions for writing and another set for reading, grouped into separate VPCs. However most of the time we apply the CQRS pattern to a back-end system or comparatively big module of the system.
Now that the idea behind the CQRS pattern is getting its shape, let’s focus on each flow separately and illuminate key properties.
Commands
Command in the CQRS pattern represents an intention to do something, perform some sort of action, execute a task. Almost anything that fits well after “I want to …” will be a legit command. I want to withdraw money. I want to add a comment. I want to fly to the Moon 🌕.
Unfortunately, not all of our wishes come true. As I mentioned before a command is an intention, not a fact. For that reason, if the task you are trying to perform (aka command) is not valid at a given point in time, it has to be rejected. The rejected command won’t be executed, not even a small portion of it. Command has a transactional nature. Your transaction is either fully accomplished or not. No middle state.
A command should provide feedback on whether it was executed successfully or not. It might include information strictly related to command execution and its results. Such as reasons why the command was rejected. Command response should not contain any data it was mutating or any data that will be used for further viewing/representation.
Now let’s check how explicit command flow might look like. Command sent from consuming system. It first bumps into Command Router, which should recognize and deliver a command to the matching Command Handler. Command Handler performs an action on the Business Domain. The Business Domain will validate the requested action. If it is valid, Business Domain will perform required business operations and save results to the database if needed. Finally, Business Domain will launch a response propagation chain, letting know its caller on whether an operation was successful or not.
Queries
Similarly to commands, queries represent an intention, but in this case, it is an intention to retrieve particular information. Almost anything that you can ask your phone today is a query example. What is the weather like tomorrow? What are my last five messages?
A query is a question, a question to the system. And the system should provide a well-defined exhaustive answer. That means that query response should have only information that was queried, not less, not more. One might think it is obvious. But in the chase for better re-usability, aka DRY principle, how many times we made things more and more generic? Which made responses more and more generic…
One from the other, each query must be a single-purpose query that is tuned to answer only one single question but answer it very well.
Following these rules will make query code simple and predictable.
Improved testability. query result expectations are clear, query execution is straight-forward.
Infinite optimization potential. Since it is a single-purpose bit of code, it has infinite potential for optimization. I’m not going to touch this aspect in detail in this article, but let’s say a stored procedure per query might be a good start.
These and other factors will result in overall code maintenance cost reduction.
CQRS as an architecture
Contract
Usage of the CQRS pattern proposes a strong communication contract between the consuming system and the system implementing the pattern. Unless in front of the CQRS-based system exists an anti-corruption layer, the consuming system should be well aware of commanding and querying concepts.
This fact comes with interesting consequences. Given commands and queries are intentions, the pattern itself builds an intention-explicit contract between both systems. Pushing developers to be aware of and more carefully deliberate about the tasks that the user will be solving.
Task-Oriented Design
Quite often CQRS system will be consumed by some sort of User Interface, then the interface itself will be carrying user intentions through controls and transfer them into the system.
Let’s look at the car as a User Interface. When a driver pushes the brake pedal, his intention is very clear. Speed reduction. Pedal immediately transfers the driver’s intention. The ABS, EBD and other support systems, that were developed specifically for accomplishing one single task, start actively working to decrease the car’s speed. The brake pedal is a control in the task-oriented user interface, which transmits driver command directly to the system that will execute it.
When a driver starts the ignition car core system performs a health check query by examining all subsystems, aggregates the result and warns the driver if any problems were detected, so the driver knows that the vehicle can be operated safely. The check engine light is an indicating control in the task-oriented user interface that transmits results of the health-check query to the driver.
Price
Implementing a CQRS pattern comes along with a steep learning curve for the whole team. Opposite to what Greg Young defines as a stereotypical architecture. Stereotypical architecture is something every one of us has seen at least once. This sort of architecture is quite popular and what is more important generic, therefore most of the developers are already familiar with it, and if not it comes along with a gradual learning curve. Stereotypical architecture is simple and has a vast variety of tooling to facilitate it.
Maintaining CQRS requires a bit of self-discipline from everyone engaged in the process until a solid habit is developed. Most of the weight will fall on the shoulders of newcomers that used to work with generic web service APIs and especially those who adhere to the REST architectural constraints, aka RESTful APIs. CQRS is like RESTful API, but vice versa. The key-concept in the RESTful API is a resource, everything is built around it, around data stored in the system, around its shape. While the key-concept of the CQRS-based system is the user’s intention, it is focused on tasks that user trying to accomplish.
Finally
There are usually a lot of ways to implement the same thing. Some work better, others… others became bug factories.
If you are seeing that the problem domain doesn’t fit well into more generic architecture or you are experimenting with domain-driven design, I’d recommend you to give CQRS a shot.
It is a powerful pattern, which might give you good exposure to the problem domain as well as solve some technical and infrastructural challenges. But it is worth keeping in mind that it will come with its price and due to conceptual differences with other architectures, it will require a mental-model shift.
As a bottom line, CQRS proved itself as a simple, yet robust pattern which will be a solid step towards supple design.