Command-Query Responsibility Separation
CQRS is an architectural pattern that prescribes a strict split within an application and suggests isolating the command and query components from one another. Axon helps by providing the necessary building blocks and infrastructure to build and run these components and dispatch messages (commands, events, and queries) to the right destination within an application.
What is CQRS?
Command-Query Responsibility Separation, CQRS for short, is an architectural pattern that prescribes a strict split within an application. A split between the part which deals with processing operations and the part which deals with answering questions. “Processing operations”, the “expressions of intent” targeted towards an application, is referred to as the Command side. “Answering questions”, the “request for information” about an application's state, is what is defined as the Query side of an application. CQRS thus suggests isolating the command and query components from one another.
Separating these concerns introduces some consequences when writing software, but more importantly, it introduces several benefits. First and foremost, it gives a single purpose to a component, increasing the focus on the problem at hand. The component is thus optimized for either dealing with commands or queries. This focus is highly beneficial when developing software.
Secondly, the separation allows for dedicated deployments of the command and query side.
Thus, non-functional requirements like differing scaling requirements can be achieved. For example, does the application have high demands for handling queries? Then, scale up the number of query instances of your application without imposing any changes to the command handling part.
The same obviously holds if large amounts of state-changes should be processable; introduce more command instances.
Benefits alongside the non-functional requirements reach a little further than just scaling. Different accessibility approached and data persistence options can be selected per side of the application. A different choice of database for the Command side compared to the Query side has now become trivial as they no longer rely on one another. Requirements for a more efficient way of storing the application state and optimizing for answering queries are now a design option for an application.
The Command Model is tasked with handling the expressions of intent that an application might have. Such an expression of intent, the command, in general, is a business operation that ideally maps to specific tasks that can be performed. Upon receiving the command, the model will decide whether that task can be executed then. When handling the command, the decision-making process is driven by business logic within the domain the application resides in.
These commands can be simple state changes within the application or potentially trigger a multitude of side effects. To decide on this outcome, the Command Model will require some states. However, the state should by no means encompass all the data flowing through the application. The only information necessary for the Command Model is the information required to make the decisions; no more, no less. This focus on dealing with commands keeps the model concise, both in operations, it should be able to do and state it should keep for that task.
The Query Model also referred to as “Projection” or “View Model”, will not deal with any expressions of intent to adjust state or perform some operation. Its sole purpose is to deal with requests for information. The requests for information and the queries an application should handle can come in very different formats, ranging from lookups based on an identifier to full-text searches on large data sets. The query side of an application thus needs to support different approaches to retrieving the application's state.
Due to the decoupling of the command and query side with CQRS, we can easily get any storage format for the Query Model without influencing the Command Model’s approach to storing its state to make decisions. It also allows implementing several storage approaches tailored towards the type of query which needs to be dealt with.
Is the query performed to fill a JSON-based web page through a specific identifier? Use a denormalized view in your Query Model, which stores that page as JSON alongside the identifier in the NoSQL solution of your choosing. Are there requirements that the application should also provide a search box for users to query several documents based on keywords? Introduce a second Query Model using a search engine to support such requests.
The separation allows optimizing the query for the exact use case to be as performant, flexible, or storage efficient as possible. It will introduce data duplication and, where needed, data denormalization. However, this is very desirable, as your model will focus on what it should return as best as possible, making the overall application more concise and reliable.
Even though CQRS breaks apart the Command and Query side into dedicated components, they are still part of the same application. As such, they need to synchronize. As the Command Model is in charge of handling the commands which incur state changes, it will notify the application that such a decision has been made. There are several ways to achieve this, such as a shared data source between both models or through stored procedures. Using events as notifiers for the synchronization requirement is, however, the suggested solution.
When leveraging this Event-Driven Architecture, the Command Model would then publish an event upon (successfully) handling a command. The event will then be handled by the Query Model(s) to update its state accordingly. Following this pattern for synchronization also allows us to update the Command Model based on the events it publishes itself (see Event Sourcing).
All the above can be combined into the following diagram:
Using CQRS within your software is something AxonIQ strongly believes in. Axon Framework helps you tremendously in that respect, providing the necessary building blocks and infrastructure to create command models and dispatch all the command, event, and query messages to the right destination within your application. Doing so allows a focus on business functionality first, without thinking about how to tie everything together.
The idea to use messages to describe commands, events, and queries, deal with the Command Model, synchronize changes and retrieve data from the Query Model is an important pillar for enabling microservices. Leveraging CQRS together with messages allows a monolith first approach to your application. This solution imposes internal separation but does not prescribe the necessity for the software to consist of separate pieces from the start. This idea ties in neatly with creating business functionality first and implementing some of the non-functional requirements later.
Following this separation opens up the possibility to pull apart an application into dedicated command and query services at a later point in time, albeit for scaling out, team structure, or other non-functionals. It thus allows evolving an application towards microservices as soon as it's necessary.
September 28th, Amsterdam
Join us for the AxonIQ Conference 2023, where the developer community attends to get inspired by curated talks and networking.
September 27th, Amsterdam
The event to collaborate, discuss, and share knowledge about techniques, tools, and practices for building complex, distributed event-driven applications.