Conflict Free Replicated Data Types image
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.

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).

These three activities work together when implementing the Command Query Responsibility Segregation pattern.

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.
Event Sourcing
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.

COST OF CQRS

One of the primary criticisms of CQRS/ES is the complexity it introduces. It is a fair criticism and requires that CQRS/ES only be used when the application mandates the high degree of scalability and flexibility to offset the complexity costs. It is essential to remember; however, that CQRS allows for multiple, smaller, models that are easier to understand and modify. As is the case with Event Sourcing, CQRS/ES will significantly increase overall application storage requirements due to both the event sourcing log persistence and multiple query model data duplication. This data duplication can result in issues with data synchronization, which will require periodically rebuilding the query projections.

Summary

The primary motivation of CQRS is to isolate the command (write) model from the query (read) models. This separation is accomplished by decoupling the models and using domain command events to update any subscribing query models. Each query model is responsible for integrating the command event into its local model representation. CQRS is often coupled with Event Sourced systems to provide a more robust query mechanism that what can be supported by Event Sourced systems alone. The complementary combination of CQRS and Event Sourced systems can provide a flexible, elastically scaled, highly available, query mechanism for microservices architectures. This mechanism comes at the cost of increased complexity.

Coming Up

In our next article, we turn our focus to some common patterns found within microservice applications. We start with the Service Discovery pattern.