Photo by Pablo Heimplatz on Unsplash
In the previous
Event Sourcing article, we mentioned that one of the primary disadvantages with event sourcing comes when a single aggregate root can't answer our queries. Before we could support queries that span multiple event-sourced entities, the application would first need to derive the current state of each entity before we could even start the query process. The computational cost would incur a non-trivial performance penalty rendering this option an unlikely candidate. In this article, we introduce the
Command Query Request Segregation (CQRS) pattern to address this deficiency and demonstrate how an Event Source system can be made queryable.
With CQRS, the CQS principle is implemented with at least two separate models. One for Command operations and a second (and potentially several) for Query operations. We build our query models to optimize the data to the specific query they support. Each query model is, in essence, a materialized view for some subset of a service's queries.
The net effect is that we no longer share the same data model for reads and writes. With CQRS, the goal is to build optimized query models ( read stores )that are purpose-built for queries that span multiple services.
In the event of a network partition, writing may be disabled to enforce consistency. Because the models have been decoupled, the application can still support data query during the network partition.
Commands, Events, & Queries
Before diving into a solution to our problem lets review the three DDD domain activities which make up the API for a domain: Commands, Events, and Queries.-
Commands
Commands are requests for a service to perform some domain operation. These are the domain's verbs.
-
Events
Events are artifacts that represent a change made to the domain state. The domain emits these events after the domain state has changed. They log the "transactions" on the domain.
-
Queries
Queries represent a request of the current state of some aspect of the domain. A query always receives a response (though that response may not contain any data).
CQRS
As its name implies, Command Query Responsibility Segregation splits up the responsibility for processing commands and queries into separate systems. It originates from Bertrand Meyer's Command Query Separation (CQS) principle that states every method can be classified as either a command or a query but not both. Using CQS, asking a question (a query) should never change the answer (no query side-effects).DECOUPLING THE COMMAND MODEL FROM OUR QUERY MODEL
If you have been reading this series of articles, it should not surprise you that our goal with CQRS is to introduce more isolation. With CQRS, we are looking to increase isolation by decoupling our Command (read) and Query (write) models. By decoupling the command and query models, the system can be made more elastic and resilient.With CQRS, the CQS principle is implemented with at least two separate models. One for Command operations and a second (and potentially several) for Query operations. We build our query models to optimize the data to the specific query they support. Each query model is, in essence, a materialized view for some subset of a service's queries.
The net effect is that we no longer share the same data model for reads and writes. With CQRS, the goal is to build optimized query models ( read stores )that are purpose-built for queries that span multiple services.
CQRS and Event Sourcing
While CQRS does not require event sourcing, they are logical complements that are often used together. When CQRS and event sourcing are used together the application relies on the generation of events to persist the history of the entity and update one or more query model. Both read, and writes are optimized for their specific role within the application. Commands are applied to the command model, generating domain events which are persisted by the event sourcing system. This portion makes up the command side. The query side receives events generated by commands and denormalized models called projections are created to support domain-specific queries.Flexibility of CQRS & Event Sourcing
CQRS makes it possible to build extremely flexible query models. Because we split the read (query) and write(command) models, each side can be highly optimized for the application's needs. This can extend to the specific technology used to persist each model allows different query models to support many different persistence types (e.g., RDBMS, Document, Graph, etc.).Evolving the model
As the model evolves, it is easy for a CQRS/ES-based system to create new projections to support the model changes as well as new queries. Since the full history is captured in the event log, new projections can also be applied retroactively. Additionally, the read and write models can now evolve independently since they have been fully decoupled.Consistency, Availability & scalability with CQRS
With CQRS/ES it is possible to build very fine-grained microservices (models as microservices). CQRS allows you to further break apart the bounded context into additional microservices. Since the Command and Query models have been separated, each can live in a separate microservice if necessary. If the query requires high availability, each query projection can potentially be split into a separate microservice and be scaled elastically. It is important to remember that with very fine-grained microservices comes additional overhead that may not be justified. Just because you can do something doesn't mean you should. Make sure the application requires this level of granularity before turning everything into a microservice.Consistency in CQRS
Simple CQRS (without ES) has the same guarantees as any non-CQRS based system. When we employ a CQRS/ES based system, we have the option of implementing different consistency guarantees for both the read & write models.Command (Write) model consistency
When we develop a command model, we usually employ a strong consistency model. Strong consistency is necessary since application decisions and computations are often made based on the current state. If the consistency guarantee is insufficiently strict, applications will be working with stale data, which could result in an incorrect outcome. Fortunately, write models ofter leverages transactions, locks, and more reactive approaches like sharding to ensure appropriate consistency guarantees.Query(Read) model consistency
It is important to understand strong consistency only matters when you are writing data. Pure reads are never strongly consistent. Data can change immediately after its been read. Because a read is potentially unbounded, it is impractical to lock the data. It is best to work under the assumption that you are always working with stale data. With the stale data assumption, eventual consistency becomes the logical option.Scalability in CQRS/ES
Because of the isolation introduced between the command and query models, both can be scaled independently. In general, most applications tend to be read-heavy. Fortunately, the query side's eventual consistency nature allows it to be scaled independently to support the larger load. Since eventual consistency is baked into the model, we can easily implement a caching layer to improve performance. If the load provides sufficient justification, the application can deploy individual microservices to host different projections for further scalability. When we deploy projection-specific services, we can support multiple instances elastically to handle the query load. The CQRS/ES command(write) model's requirement for stronger consistency usually necessitates a consistency technique like sharding to support scaling.Availability in CQRS
Due to the write model's need for strong consistency, the application usually must sacrifice availability. Fortunately, we can get high availability from the read side, thanks to its eventually consistent model.In the event of a network partition, writing may be disabled to enforce consistency. Because the models have been decoupled, the application can still support data query during the network partition.
Twitter
Facebook
Reddit
LinkedIn
Email