The Command and Query Responsibility Segregation (CQRS) Pattern
The Command and Query Responsibility Segregation (CQRS) it’s an architectural pattern where the main focus is to separate the way of reading and writing data. This pattern uses two separate models:
- Queries — Which are responsible for reading data
- Commands — Which are responsible for writing data
The Command and Query Responsibility Segregation (CQRS) pattern separates read and update operations for a data store. Implementing CQRS in your application can maximize its performance, scalability, and security.
The image below illustrates a basic implementation of the CQRS Pattern:
Commands
Commands represent the intention of changing the state of an entity. They execute operations like Insert, Update, Delete. Commands objects alter state and do not return data.
Commands represent a business operation and are always in the imperative tense, because they are always telling the application server to do something. A command is an object with a name of an operation and the data required to perform the operation. For example:
public class DeactivateProductCommand : ICommand
{
public readonly int InventoryItemId
public readonly string Comment; public DeactivateProductCommand(int id, string comment)
{
IventoryItemId = id;
Comment = comment;
}
}
The commands are interpreted by the CommandHandlers and they return an event, which can be a successful event or a failure event. If the command succeeds it will create a successful event, and if the command fails it will create a failure event.
Queries
Queries are used to get data from the database. Queries objects only return data and do not make any changes.
Queries will only contain the methods for getting data. They are used to read the data in the database to return the DTOs to the client, which will be displayed in the user interface.
Queries usually start with the word Get, because queries ask for the application to provide some data, for example:
public class GetProductByIdQuery() : IQuery
{
public int Id { get; set; } public GetProductByIdQuery(int id)
{
Id = id;
}
}
The queries are interpreted by the QueryHandlers and return query values.
Separated Databases
When working with CQRS, it’s also possible — but it’s not mandatory — to have separate databases: a database only for reading the data, and a database only for making changes.
In traditional architectures, the same data model is used to query and update a database, and this is great for basic CRUD operations (remember of the KISS principle — if you want to read more about it, click here), but when we deal with more complex applications like microservices or any kind of application that has a high demand for data consumption, the common approach can become unwieldy, because having much writing and reading in the same database can affect the performance of the application (read and write workloads have very different performance and scale requirements). In this scenario you can use a database only for reading — and you can replicate this database to have more performance — and a database just for writing, where everything that will be written in this database will be replied to the reading database.
The queries are used to execute the reading operations in the reading database and the commands are used to execute the change operations in the database that is used for update. In this case, it’s necessary to keep those databases in sync, one way of doing this is through the use of events. Always when the command is called and changes something in the writing database, a process needs to be called to update the modified data in the reading database.
It’s also possible to work with messaging together with CQRS, in this case, the commands will go to a queue and the domain can read and handle those commands.
The image below illustrates the CQRS Patter using a database for reading and a database for updating:
When the CQRS can be used?
CQRS can be considered to be used on scenarios where:
- Has a high demand for data consumption (high volumes of reads and writes), or require the ability to scale the read and write paths separately. For example, let’s say your application has a high demand for reading, and not that much for writing. In this case, you can have two databases (one for reading and one for writing), and you can independently scale up/down your database (with that you will have eventual consistency, as it can take some time until your reading database is updated with the latest change in the writing database). Another approach to achieve independent scaling is by using a message queue or event bus, in order to decouple the command and query components, this allows you to scale each component independently by adding more consumers to the queue or event bus.
- Performance of data reads must be tuned separately from the performance of data writes, especially when the number of reads is much greater than the number of writes.
- There is the need for one team to focus on the complex domain model that is part of the write model, while another team can focus on the read model and the user interfaces.
- The system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.
- Integration with other systems, especially in combination with event sourcing, where the temporal failure of one subsystem shouldn’t affect the availability of the others.
When the CQRS should not be used?
CQRS is not recommended in scenarios where:
- The domain or the business rules are simple (remember the KISS principle).
- A simple CRUD-style user interface and data access operations are sufficient.
Benefits of CQRS
As we saw before, CQRS can be very handy for some scenarios, and those are some benefits of CQRS:
- With the separation of concerns, helps to minimize and manage the complexity, making the application more maintainable, extensible, and flexible.
- Segregating the responsibility between commands and queries can help to improve performance, scalability, and security.
- The workloads between reading and writing operations will be different and using CQRS allows you to scale each of them independently of the others.
- Security is improved because you will have a single object model to execute an update operation ensuring that only the right classes will do it.
- Because each class is responsible for reading or writing, this reduces the chance of exposing data that should not available to a particular user.
Disadvantages of CQRS
Implementing CQRS also has some disadvantages that must be taken into account:
- Increase the complexity of the code.
- If using messaging to process commands and publish update events, the application must handle message failures or duplicate messages.
- If you separate the read and write databases, the read database may be stale, and it can be difficult to detect when a user has issued a request based on stale read data. Even if you use event-sourcing or some other mechanism to keep the databases sync, there will be some time delay (even if it’s a small one) before the writing database be consistent. So you should consider that maybe you can be reading data that is stale.
Event Sourcing
The CQRS pattern is often used together with the Event Sourcing Pattern (they work very well together). This pattern will be explained with more details in another article, but just to give a brief introduction, with this pattern the application state is stored as a sequence of events, and each event represents a set of changes to the data. This pattern allows us to keep all the states of an entity since it’s creation. With Event Sourcing, we can have not only the history of all changes but also the sequence in which those changes were made in an entity.
Event Sourcing ensures that all changes to application state are stored as a sequence of events. Not just can we query these events, we can also use the event log to reconstruct past states, and as a foundation to automatically adjust the state to cope with retroactive changes. — Martin Fowler
For example, let’s suppose that you have a Product entity with a property “Name”, and on this property, you have the value “Table”. If you change the value for the property name from “Table” to “Chair”, when you get the data from this entity you will just have the new information. But when we work with Event Source, it’s possible to see the older values, because then we have a history of all changes that were made in the entity.
The CQRS pattern does not demand that Event Sourcing must be implemented, neither the Event Sourcing demand that the CQRS must be implemented, you can work with those patterns together or separately.
Although the CQRS can be used without events, they do complement each other so its common for systems that use CQRS to leverage the use of events. Events are one way to effectively communicate state changes so that the query model can stay up to date as the command model updates data.
Conclusion
The CQRS pattern can be very handy for more complex applications or application where we have a high demand for data consumption. For more commons scenarios where just basic operations will be executed, the CQRS can create unnecessary complexity to the application. So before deciding if you will implement CQRS or not, you always should consider what your application demands.
References
Command and Query Responsibility Segregation (CQRS) pattern — Microsoft Docs
Software Architect’s Handbook — Joseph Ingeno
CQRS Documents by Greg Young