Command and Query Responsibility Segregation or CQRS is a pattern that separates read and write operations for a data store.
CQRS requires commands to modify data and queries to only read it.
The term CQRS was coined by ↑ Greg Young. ↑ CQRS Documents by Greg Young.
- CQRS
↑ А какие виды CQRS вы знаете?.
There are two ways to organize application layer, aka Interactors in Clean Architecture:
- Horizontal way: services
- Vertical way: CQRS handlers, ↑ vertical slices
The largest handlers can reach 500 lines of code. It's still several times less than number of lines in a service. Normally handler is much less than 500 lines of code. For a service to have from few hundreds to few thousands lines of code is an usual thing.
It's much easier to maintain a small handler with well-defined scope of responsibility than a big service.
The SRP principle states that a module should have one, and only one, reason to change. A handler is responsible for one business operation, so it complies with the SRP principle.e.
Architecture based on handlers expands by adding new handlers and not by rewriting existing handlers.
Handlers are independent from each other. All the code of a business operation will be in one place, in one class — in a handler.
On the other hand operations can be spread across multiple services. One service can call other services. Operations inside a service get fragmented.
It's a rare thing to see more than 5 dependencies inside of a handler. It's because handler has only dependencies it needs for implementing its business operation. For a service to have 10 or more dependencies is a usual thing.
In CQRS community there is an argument: is it ok to call a command or a query from inside handler or not? Spoiler: it's ok. BTW in services community there is no such an argument at all.
With handlers there will be more infrastructural code. You'll have more classes: commands, handlers. For each handler you'll have to specify constructor and pass its dependencies. You have to get used to it and it's not a problem overall.
IReadOnlyDatabaseContext.cs
:
public interface IReadOnlyDatabaseContext
{
DbSet<Order> Orders { get; }
}
Read-only database context above:
- Has no
SaveChangesAsync
method - Connection string points to database slave nodes
- Is used inside query handlers
IDatabaseContext.cs
:
public interface IDatabaseContext : IReadOnlyDatabaseContext
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
Write database context above:
- Connection string points to database master node
- Is used inside command handlers
DatabaseContext.cs
:
internal class DatabaseContext : DbContext, IDatabaseContext
{
public DbSet<Order> Orders { get; set; }
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
}
}
ReadOnlyDatabaseContext.cs
:
internal class ReadOnlyDatabaseContext(IDatabaseContext databaseContext) : IReadOnlyDatabaseContext
{
public IQueryable<Word> Orders => databaseContext.Orders.AsNoTracking();
}
1. Query is not allowed to change state: it can't even write logs.
It's a myth, because in query we must not change the state of the domain model, but we can:
- Write logs
- Gather metrics
- Update cache, etc.
2. Query can read only read model.
A read model is a thing from where CQRS query reads its data. Examples:
- Materialized view
- Denormalized data that's update asynchronously in the background
- JSON stored in database
- Any specialized store, for example, Elasticsearch
Generally speaking a read model is a storage that is updated asynchronously. In this case you have some lag between the time when you had written something to database and when you can read this thing from database.
It's a myth, because query can also read domain model, but it must not change it.
3. Command can read only domain model.
It's a myth — command can query read model as well. Keep in mind though that because of the lag you may not get inside of the command the latest write from read model.
Although reading read model from command is a myth it's better not to do it.
4. Command can not return value.
Other variations of this myth:
- Command must return
void
- Command can return some ID of operation but it must not return any data.
- Command is an asynchronous operation. For example you trigger report build inside of a command and than you wait until report is formed and sent to your email asynchronously.
Command and Query Responsibility Segregation, CQRS, originated with ↑ Bertrand Meyer's ↑ Command and Query Separation Principle. Wikipedia defines the principle as:
It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.
Basically it boils down to: if you have a return value you cannot mutate state. If you mutate state your return type must be void. There can be some issues with this. Martin Fowler ↑ shows one example on the ↑ bliki with:
Meyer likes to use command-query separation absolutely, but there are exceptions. Popping a stack is a good example of a modifier that modifies state. Meyer correctly says that you can avoid having this method, but it is a useful idiom. So I prefer to follow this principle when I can, but I'm prepared to break it to get my pop.
Command and Query Responsibility Segregation was originally considered just to be an extension of this concept. For a long time it was discussed simply as CQS at a higher level. Eventually after much confusion between the two concepts it was correctly deemed to be a different pattern.
Command and Query Responsibility Segregation uses the same definition of Commands and Queries that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is that in CQRS objects are split into two objects, one containing the Commands one containing the Queries.1
Everyone always talks about CQRS and event sourcing when, really, it's event sourcing and CQRS. When I first started teaching people about CQRS and event sourcing it was advantageous to teach them CQRS first, and then teach them event sourcing. You can use CQRS without event sourcing, but with event sourcing you must use CQRS.
↑ Greg Young - CQRS and Event Sourcing - Code on the Beach 2014.
I have been talking about CQRS and event source subjects for a very, very long time. The first time I talked about this was in 2006 at Q Con San Francisco.
With classical horizontal service architecture application layer organization we create a separate service for each entity in our domain and each public method of this service is a business operation: create order, get orders, etc:
public class OrderService
{
public Order CreateOrder()
{
// Create order here
}
public List<Order> GetOrders()
{
return _databaseContext.Orders;
}
}
With this approach every entity will have its own service and all the services reside inside of one component:
├── ApplicationServices.proj
├── OrderService.cs
└── UserService.cs
With this approach for each business operation a command or a query is created with corresponding handler:
public class GetOrdersQuery
{
}
public class GetOrdersQueryHandler
{
public List<Order> Handle(GetOrdersQuery request)
{
return _databaseContext.Orders.Find(request.Id);
}
}
With this approach folder structure is going to be as follows:
├── ApplicationLayer.csproj
├── Commands
| └── CreateOrder
| |── CreateOrderCommand.cs
| └── CreateOrderCommandHandler.cs
├── Dtos
| |── CreateOrderDto.cs
| └── GetOrdersDto.cs
├── Queries
| └── GetOrders
| |── GetOrdersQuery.cs
| └── GetOrdersQueryHandler.cs
├── Utils
└── OrderMappingProfile.cs
Also we use the same model, for reading and writing data; model in DDD sense. Commands return values.
Described above approach is also know as ↑ Vertical Slice Architecture, the term coined by Jimmy Bogard.
1 CQRS Documents by Greg Young, page 17.