Command-Query Separation (CQS) is a principle or guideline, used in a software architecture, that 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. Methods should return a value only if they create no side effects.
CRUD is an acronym for Create, Read, Update and Delete. Most modern software applications are built using CRUD. When using CQS; create, update and delete are considered to be commands since they mutate state, whereas Read is considered to be a query since it will never mutate state. In other words, any method that mutates state must be a command and any method that never mutates state must be a query.
Command Query Responsibility Segregation (CQRS) is a principle that applies CQS however it is a much more complex principle since CQRS allows for separate data stores and separate models for commands and queries. The data between data stores is usually synchronized using a service bus. With CQS, the same data store is used for both commands and queries and models may be shared between commands and queries.
CQS is a fairly simple pattern adding more structure, readability and test-ability to a software architecture. It adheres well to the SOLID principles and promotes low coupling and high cohesion.
Consider the below software architecture using the CQS principle:
The above architecture diagram presents a clear separation for commands and queries, that is, code for mutating data and code for reading data are completely separated. This logical separation increases the level of separation of concerns.
A command is created when data is to be mutated. The following classes are needed for a command:
- A command class containing the properties required to mutate data. For example,
CreateHeroCommandis a command class that contains the properties to create a new hero.
- A command handler class to handle the business logic for the command and to handle mutation of the data store. For example,
CreateHeroCommandHandleris a class that handles the creation of a new hero.
- A command response class to return a response to the caller. For example,
CreateHeroCommandResponseis a class that contains the details of the newly created hero. Note that a command response class is optional since not all commands will return a response.
- A command validator class to handle validation for a command. This class should be created using
FluentValidationprovides an easy to use fluent interface to validate objects. The validator is to be called before the command handler is executed since the command handler always assumes that the command is valid.
CreateHeroCommandValidatoris an example of a validator for a
A query is only created when data is to be retrieved from the data store. Similar to commands, the following classes are needed for a query:
- A query class containing the properties needed to retrieve data from a data store. For example,
GetHeroByIdQueryis a query class that contains the
Idproperty of the hero to get from the data store. Note that not all queries will have properties.
- A query handler class to retrieve data from the data store. For example,
GetHeroByIdQueryHandleris a class that handles the logic for getting a hero by
Idfrom the data store.
- A query response class to return a response to the caller. For example,
GetHeroByIdQueryResponseis a class that contains the details of the hero.
- A query validator class to handle validation for a query. This class should be created using
FluentValidationprovides an easy to use fluent interface to validate objects. The validator is to be called before the query handler is executed since the query handler always assumes that the query is valid.
GetHeroByIdQueryValidatoris an example of a validator for a
GetHeroByIdQuery. Note that the query validator class is optional since not all queries will contain properties.
The service interface is responsible for creating commands or queries based on a callers request. After creating a command or query, the service interface is responsible for ensuring that the command or query is validated before invoking the appropriate handler.
Best Practices when using CQS
Since commands and queries are logically separated and require a number of classes to handle an action, a lot of boilerplate code is required to wire up an application. Two design patterns that drastically reduce boilerplate code when implementing CQS are:
- The mediator design pattern – defines an object which encapsulates how a set of objects interact with each other. The
nugetcan be used to implement this design pattern.
- Dependency injection – a technique used to help inject dependent objects of a class. There are a number of dependency injection packages that assist with dependency injection. Choosing the appropriate dependency injection package normally comes down to personal preference.
The above design patterns are not mandatory, however implementing them with a robust third party tool saves a development team a lot of time.
Example implementation of CQS
For this example, a hero micro-service will be built using an Azure Function app. This article will only cover the implementation of one command and one query. The full working source code can be found here.
The software architecture for the micro service uses the onion architecture and CQS principle.
Microsoft.Azure.Functions.Extensions packages are used. The data store will be mocked since the purpose of this article is to illustrate an implementation of the CQS principle.
The heroes micro-service is required to have the following endpoints:
GET /heroes GET /heroes/:id POST /heroes/ PUT /heroes/:id DELETE /heroes/:id
To create a hero, only a name is required. Thus, create a new
CreateHeroCommand class as follows:
The command handler will need to perform basic business rules, that is check if the hero exists before creating the new hero in the data store. The
CreateHeroCommandHandler is implemented as follows:
Notice that an instance of the
IHeroRepository will be injected using dependency injection.
When creating a new hero, the details of the new hero will be returned to the caller, thus the
CreateHeroCommandResponse class is implemented as follows:
CreateHeroCommandValidator class is created to ensure that a
CreateHeroCommand contains a valid hero name:
Queries are created in a similar fashion. To get a hero by
Id, create the following classes:
To complete the commands and queries,
GetAllHeroesQuery are to be implemented. The solution should look similar to this:
Below is an implementation of the get hero by id endpoint and the create hero endpoint exposed by the heroes function app:
The final part of the implementation is to ensure dependency injection is set up:
Command-Query Separation (CQS) is a simple and powerful pattern that introduces more structure, readability and test-ability to a software architecture.
Today, most Agile methodologies are encouraging rapid development to implement or enhance features as quickly as possible. To embrace this, the software architecture of the system needs to be simple and easy to extend, that is, without breaking existing features. The onion architecture along with the CQS pattern provide a software architecture that is simple, robust, maintainable, testable and extensible. This solution fits perfectly in an Agile world.
The source code used in this article can be found here.
Further information on CQS can be found at the following links:
Software versions used in this article
- Visual Studio 2019 v4.8.03752
- Microsoft.Azure.Functions.Extensions v1.0.0
- Microsoft.NET.Sdk.Functions v3.0.7
- .NET Core v3.1
- Postman v7.24.0
- FluentValidation v8.6.2
- MediatR v8.0.0
- theCodeReaper.MediatR.DDD v1.0.2
- ReSharper Ultimate v2020.1.1