This article is based on material from various articles on CQRS, as well as projects where this approach was applied.
Management systems of enterprises, projects, employees have long entered our lives. And users of such enterprise applications are increasingly demanding: requirements for scalability increase, complexity of business logic, requirements for systems change quickly, and reporting is required in real time.
Therefore, when developing, it is often possible to observe the same problems in the organization of code and architecture, as well as in their complexity. With the wrong approach to design, sooner or later there may come a moment when the code becomes so complicated and confusing that every change requires more time and resources.
')
Typical approach to the design of the application
Layered architecture is one of the most popular ways to organize a web application structure. In its simple variation, as in the above diagram, the application is divided into 3 parts: the data layer, the business logic layer and the user interface layer.
There is a Repository in the data layer that abstracts us from the data store.
In the business logic layer, there are objects that encapsulate business rules (usually their names vary within Services / BusinessRules / Managers / Helpers). User requests pass from the UI through the business rules, and further through the Repository work with the data storage is performed.
With this architecture, requests for receiving and changing data are usually made in the same place - in the business logic layer. This is a fairly simple and familiar way of organizing code, and such a model can be suitable for most applications, if in these applications the number of users does not change significantly over time, the application does not experience large loads and does not require significant expansion of functionality.
But if a web resource becomes quite popular, it may be a question that one server is not enough for it. And then the question arises about the distribution of the load between multiple servers. The easiest way to quickly distribute the load is to use multiple copies of the resource and database replication. And given that all the actions of such a system are not divided in any way, this creates new problems.
Classic multi-layered architecture does not provide an easy solution to such problems. Therefore, it would be nice to use approaches in which these problems are solved from the very beginning. One such approach is CQRS.
Command and Query Responsibility Segregation (CQRS)
CQRS is a software design approach in which code that changes state is separated from code that simply reads that state. This separation may be logical and based on different levels. In addition, it can be physical and include different links (tiers), or levels.
The basis of this approach is the principle of Command-query separation (CQS).
The basic idea of ​​CQS is that there are two types of methods in an object:
- Queries: Methods return a result without changing the state of the object. In other words, Query has no side effects .
- Commands: Methods change the state of an object without returning a value.
For an example of this separation, consider the User class with the single method IsEmailValid:
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
public class User { public string Email { get ; private set ; } public bool IsEmailValid( string email) { bool isMatch = Regex .IsMatch( "email pattern" , email); if (isMatch) { Email = email; // Command } return isMatch; // Query } }
In this method, we ask (do Query) whether the transmitted email is valid. If yes, then we get in response to True, otherwise False. In addition to returning the value, it is also determined here that in the case of a valid email, immediately assign its value (do the Command) to the Email field.
Despite the fact that the example is rather simple, a less trivial situation is likely, if we imagine the Query method, which, when called in several levels of nesting, changes the state of different objects. At best, lucky if you do not have to deal with similar methods and their long debugging. Similar side effects from calling Query are often discouraged, as it is difficult to understand the system.
If we use the principle of CQS and divide the methods into Command and Query, we get the following code:
- public class User
- {
- public string Email { get ; private set ; }
- public bool IsEmailValid ( string email) // Query
- {
- return Regex .IsMatch ( "email pattern" , email);
- }
- public void ChangeEmail ( string email) // Command
- {
- if (IsEmailValid (email) == false )
- throw new ArgumentOutOfRangeException (email);
- Email = email;
- }
- }
Now a user of our class will not see any state changes when calling IsEmailValid, he will only get the result - whether the email is valid or not. And in the case of calling the ChangeEmail method, the user explicitly changes the state of the object.
In CQS, Query has one feature. Since Query does not change the state of an object in any way, then methods like Query can be well parallelized by dividing the load on read operations.
If CQS operates on methods, then CQRS rises to the object level. To change the state of the system, the
Command class is created, and to select data, the
Query class. Thus, we get a set of objects that change the state of the system, and a set of objects that return data.
Typical system design, where there is a UI, business logic and database:

CQRS says that you don’t need to mix Command and Query objects, you need to explicitly select them. A system divided in this way will look like this:

The separation pursued by the CQRS is achieved by grouping query operations in one level, and commands in another. Each level has its own data model, its own set of services and is created using its own combination of patterns and technologies. More importantly, these two levels can even be in two different links (tiers) and be optimized separately, without affecting each other.
Just understanding that commands and requests are different things has a profound effect on software architecture. For example, it suddenly becomes easier to foresee and code each level of the domain. The domain layer in the command stack only needs data, business rules, and security rules to perform tasks. On the other hand, the level of the subject area in the query stack may be no more complicated than a direct SQL query.
How to start with CQRS?
1. Team stackIn CQRS, only the execution of tasks that modify the state of the application is assigned to the command stack. The team has the following properties:
- Changes the state of the system;
- Returns nothing;
- The command context stores the data needed to execute it.
The announcement and use of the command can be divided into 3 parts:
- A command class that represents the data;
- Class command handler;
- A class with a method or methods that take a command at the input and call exactly the handler that implements the command with the given type.
The essence of commands and requests is that they have a common feature by which they can be combined. In other words, they have a common type. In the case of commands, it will look like this:
- public interface ICommand
- {
- }
The first step is declared interface, which, as a rule, contains nothing. It will be used as a parameter, which can be obtained on the server side directly from the user interface (UI), or otherwise formed to send a command to the handler.
Next, a command handler interface is declared.
- public interface ICommandHandler < in TCommand> where TCommand: ICommand
- {
- void Execute (TCommand command);
- }
It contains only 1 method that accepts data with the type of interface declared earlier.
After that, it remains to determine the method of centralized invocation of command handlers depending on the specific type of command transmitted (ICommand). This role can be performed by a service bus or a dispatcher.
- public interface ICommandDispatcher
- {
- void Execute < TCommand > ( TCommand command) where TCommand : ICommand;
- }
Depending on the needs, he can have one or more methods. In simple cases, it may be sufficient to have one method whose task is to determine by the type of the passed parameter which implementation of the command handler to invoke. Thus, the user does not have to do it manually.
Sample command . Suppose there is an online store, for it you need to create a team that will create a product in the store. In the beginning, we will create a class, where in its name we indicate what action this command performs.
- public class CreateInventoryItem : ICommand
- {
- public Guid InventoryItemid { get ; }
- public string Name { get ; }
- public CreateInventoryItem ( Guid inventoryItemld, string name)
- {
- InventoryItemId = inventoryItemId;
- Name = name;
- }
- }
All classes that implement ICommand contain data — properties and a constructor with their values ​​set during initialization — and nothing more.
The implementation of the handler, that is, the command itself, is reduced to fairly simple actions: a class is created that implements the ICommandHandler interface. The type argument specifies the previously declared command.
- public class InventoryCommandHandler : ICommandHandler < CreateInventoryItem >
- {
- private readonly IRepository < InventoryItem > _repository;
- public InventoryCommandHandlers (IRepository < InventoryItem > repository)
- {
- _repository = repository;
- }
- public void Execute ( CreateInventoryItem message)
- {
- var item = new InventoryItem (message.InventoryItemld, message.Name);
- _repository.Save (item);
- }
- // ...
- }
Thus, we implement a method that accepts this command as input, and we indicate what actions we want to perform based on the transmitted data. Command handlers can be combined logically and implement several ICommandHandler interfaces with one type of command in one such class. This will result in overloading of methods, and when calling the Execute method, an appropriate command type will be selected.
Now, to call the appropriate command handler, you need to create a class that implements the ICommandDispatcher interface. Unlike the last two, this class is created once and can have different implementations depending on the strategy of registering and invoking command handlers.
- public class CommandDispatcher : ICommandDispatcher
- {
- private readonly IDependencyResolver _resolver;
- public CommandDispatcher (IDependencyResolver resolver)
- {
- _resolver = resolver;
- }
- public void Execute < TCommand > ( TCommand command) where TCommand : ICommand
- {
- if (command == null ) throw new ArgumentNullException ( "command" );
- var handler = _resolver. Resolve < ICommandHandler < TCommand >> ();
- if (handler == null ) throw new CommandHandlerNotFoundException ( typeof ( TCommand ));
- handler.Execute (command);
- }
- }
One way to call the desired command handler is to use a DI container, in which all implementations of handlers are registered. Depending on the transferred command, the instance will be created that processes this type of command. Then the dispatcher simply calls its Execute method.
2. Request stackThe request stack deals only with data retrieval. Requests use a data model that best matches the data used at the presentation level. You hardly need any business rules — they usually apply to commands that change state.
The request has the following properties:
- Does not change the state of the system;
- The request context stores the data necessary for its execution (paging, filters, etc.);
- Returns the result.
Announcement and use of requests can also be divided into 3 parts:
- The query class that represents the data with the return type;
- Request handler class;
- A class with a method or methods that accept an input request and call exactly the handler that implements the request with the given type.
As well as for commands, similar interfaces are declared for requests. The only difference is that they indicate what should be returned.
- public interface IQuery < TResult >
- {
- }
Here, the type of the returned data is specified as the type argument. This can be an arbitrary type, for example, string or int [].
After the request handler is declared, the return type is also indicated.
- public interface IQueryHandler < in TQuery, out TResult > where TQuery: IQuery < TResult >
- {
- TResult Execute (TQuery query);
- }
Similar to commands, a dispatcher is declared to invoke the request handlers.
- public interface IQueryDispatcher
- {
- TResult Execute < TQuery , TResult > ( TQuery query) where TQuery : IQuery < TResult >;
- }
Request example. Suppose you need to create a query that returns users by search criteria. Here also with the help of a meaningful class name we indicate what the request will be made.
- public class FindUsersBySearchTextQuery : IQuery < User []>
- {
- public string SearchText { get ; }
- public bool InactiveUsers { get ; }
- public FindUsersBySearchTextQuery ( string searchText, bool inactiveUsers)
- {
- SearchText = searchText;
- InactiveUsers = inactiveUsers;
- }
- }
Next, create a handler that implements IQueryHandler with arguments of the request type and the type of its return value.
- public class UserQueryHandler : IQueryHandler < FindUsersBySearchTextQuery , User []>
- {
- private readonly IRepository < User > _repository;
- public UserQueryHandler (IRepository < User > repository)
- {
- _repository = repository;
- }
- public User [] Execute ( FindUsersBySearchTextQuery query)
- {
- var users = _repository.GetAll ();
- return users.Where (user => user.Name.Contains (query.SearchText)). ToArray ();
- }
- }
After that, it remains to create a class to call the request handlers.
- public class QueryDispatcher : IQueryDispatcher
- {
- private readonly IDependencyResolver _resolver;
- public QueryDispatcher (IDependencyResolver resolver)
- {
- _resolver = resolver;
- }
- public TResult Execute < TQuery , TResult > ( TQuery query) where TQuery : IQuery < TResult >
- {
- if (query == null ) throw new ArgumentNullException ( "query" );
- var handler = _resolver. Resolve < IQueryHandler < TQuery , TResult >> ();
- if (handler == null ) throw new QueryHandlerNotFoundException ( typeof ( TQuery ));
- return handler.Execute (query);
- }
- }
Call commands and requests
In order to be able to call commands and requests, it is enough to use the appropriate dispatchers and send a specific object with the necessary data. In an example, it looks like this:
- public class UserController : Controller
- {
- private IQueryDispatcher _queryDispatcher;
- public UserController (IQueryDispatcher queryDispatcher)
- {
- _queryDispatcher = queryDispatcher;
- }
- public ActionResult SearchUsers ( string searchString)
- {
- var query = new FindUsersBySearchTextQuery (searchString);
- User [] users = _queryDispatcher.Execute (query);
- return View (users);
- }
- }
Having a controller for processing user requests, it is enough to transfer the object of the desired dispatcher as a dependency, then create the request object or command object and transfer it to the dispatcher's Execute method.
So we get rid of the need to constantly increase dependencies with an increase in the number of system functions and reduce the number of potential errors.
Register HandlersYou can register handlers in different ways. With the help of the DI-container you can register separately or automatically looking at the assembly with the necessary types. The second option might look like this:
using SimpleInjector;
var container = new Container ();
container.Register( typeof (ICommandHandler<>), AppDomain .CurrentDomain.GetAssemblies());
container.Register( typeof (IQueryHandler<,>), AppDomain .CurrentDomain.GetAssemblies());
It uses the container
SimpleInjector . When registering handlers with the Register method, the first argument specifies the type of interfaces for command and request handlers, and the second argument is the assembly that searches for classes that implement these interfaces. Thus, it is not necessary to specify specific handlers, but only a common interface, which is extremely convenient.
What if when you call Command / Query you need to check access rights, write information to the log, and the like?
Their centralized call allows you to add actions before or after the execution of the handler, without changing any of them. It is enough to make changes to the dispatcher itself, or to create a decorator, which, through a DI-container, replaces the original implementation (in the SimpleInjector documentation, examples of such
decorators are described quite well).
Advantages of CQRS- Fewer dependencies in each class;
- The principle of sole responsibility (SRP) is respected;
- Fits almost everywhere;
- Easier to replace and test;
- Easier expands functionality.
CQRS restrictions- When using CQRS, many small classes appear;
- When using a simple CQRS implementation, it can be difficult to use a group of commands in a single transaction;
- If common logic appears in Command and Query, you need to use inheritance or composition. This complicates the design of the system, but for experienced developers is not an obstacle;
- It’s hard to stick with CQS and CQRS. The simplest example is a method to fetch data from a stack. Data sampling is Query, but we need to change the state and make the stack size -1. In practice, you will seek a balance between strict adherence to principles and production necessity;
- Bad lies on CRUD-applications.
Where not suitable- In small applications / systems;
- In predominantly CRUD applications.
Conclusion
For applications to be truly effective, they need to adapt to the requirements of the business. The CQRS-based architecture greatly simplifies the expansion and modification of working business processes and supports new scenarios. You can manage extensions in complete isolation. For this, it is enough to add a new handler, register and tell it how to process messages of the required type only. The new component will be automatically called only when the corresponding message appears and work side by side with the rest of the system. Easy, simple and efficient.
CQRS allows you to optimize the pipeline of commands and requests in any way. At the same time, optimization of one pipeline will not break the work of another. In the most basic form of CQRS, one common database is used and different modules are called for read and write operations from the application layer.
Sources→
Alexandre Bindu's blog - CQRS in practice→
At the forefront - CQRS for a regular application.→
How we tried DDD, CQRS and Event Sourcing and what conclusions did→
CQRS Documents by Greg Young→
Simple CQRS example→
DDDD, CQRS and Other Enterprise Development Buzz-words