TL;DR: CQRS is about not mixing "read" and "write" logic. You can use patterns like a Repostiory / Search Engine and Command Bus to help you achieve this.
The Single Responsibility Principle (SRP) has many applications: we can find it in the Unix philosophy ("do one thing and do it well") and usually refactoring is all about identifying responsibilities and extracting them in their own classes.
Command/Query Responsibility Segregation (CQRS) can be viewed as a part of it.
In this article, we'll explore the pros and cons of CQRS and we'll see some ways to implement it.
We usually communicate with our application by giving it an input which can be viewed as a "message". Messages can be classified in 3 different types:
- Imperative: we want the application to do something (e.g. register a new member)
- Interrogatory: we want to know the state of the application (e.g. the list of registered members)
- Informative: we notify the application of an event (e.g. a member visited a page)
The CQRS principle states that Imperative messages should not be mixed with Interrogatory ones.
Note: Here's an interresting article about validating those messages.
Asynchronous use case
Let's take the A/B testing example: we'd like to know which setting will attract more visits on a given page. To do so when a user visits the page, we send a request to an API with some helpful information.
On receiving the request, the API can simply push a message to a Messaging Queue (e.g. RabbitMQ). This request was an Informative message.
Eventually, the Messaging Queue will call a consumer and give it the message: now the consumer must register the information somewhere. This message is an Imperative one.
Later on, we can display the statistic on a dashboard. This time, we're dealing with an interrogatory message.
As we can see, the CQRS principle is applied here. It is really useful whith asynchronous use cases.
Synchonous use case
Another example would be member registration: a new member submits a registration form. If it is valid, they're told to check their email: a confirmation token has been sent to them.
Behind the scene, our application receives the request: it is an Imperative message so we create a token and save the information somewhere. Once done the application can send a "Member registered" event, which will trigger a listener that will send the email.
Note: the registration logic can be decoupled from the email logic: we can first have a service that registers members and when done sends a "Member registered" event. Then a listener could call our email service.
Once again, we've applied the CQRS principle, but in a synchronous use case. If later on our application gets successful, we'll might want to make those process asynchronous and it will be easy to do so.
Imperative messages expecting return value
Our final example will be about an API that allows scientists to report a new species. They need to send a POST Request to the endpoint, which in turn will return a response containing the created resource.
The issue here is that we're going to mix an Imperative message (report a new species) and an Interrogatory message (get the newly reported species).
CQRS cannot be applied "fully" everywhere, but we can try the following compromises:
- our entry point can first handle the imperative message, and then handle the interrogatory one
- our imperative message handler can return directly the result without extra "read" queries
Note: for the second solution, we need to create our own UUID, instead of relying on the database to generate the IDs.
Handling Imperative Messages
The Command Bus pattern is really helpful to handle Imperative Messages. It's composed of 3 objects:
- Command: a Data Transfer Object (DTO) with a meaningful name (e.g. ReportNewSpecies) that wraps the message parameters
- CommandHandler: a service dedicated to a single Command, that encapsulates the logic
- CommandBus: contains all CommandHandlers and calls the appropriate one for the given Command
The Command would be constructed in an entry point (Controller, Command, EventListener), with parameters extracted from the input (Request, Input, Event) and then given to the CommandBus.
The CommandBus is usually a Middleware:
- we can have a simple CommandBus that calls the apprioriate command
- we can have a CommandBus that wraps the simple one in a database transaction
- we can have a CommandBus that wraps the transaction one in an exception handler
Note: To learn more about this pattern, have a look at the following articles:
Handling Interrogatory Messages
There's actually many options to handle Interrogatory messages.
The Repository design pattern introduces a class that acts like a collection, but behind the scene relies on a Gateway to actually retrieve data (could be from a file, a database, a remote endpoint etc) and on a Factory to format the returned value.
Usually "find" methods are added to the repository with a name describing the expected criterias.
Note: Here's a list of nice articles about this parttern:
Sometimes the repositories will grow bigger and bigger. The alternative solution would be to build a Criteria object and pass it to a service.
Here's some of these solutions:
- Using the QueryBuilder
- build a Criteria and give it to the repository
- using the specification pattern
- RulerZ, a library using the specification pattern
Personally I've been experimenting with a Proof Of Concept (POC): SearchEngine. I still need more experimentations to start advising on this subject, so if you want to share your experience you'd be welome to post a comment :) .
CQRS helps you to decouple your code (from itself and from third party libraries) even if, like every principles, it cannot be applied everywhere.
To help you apply it, you can use Command Bus and Repositories / SearchEngine.
Note: Here's a list of interresting articles about CQRS: