If we use Event Sourcing, then reading data from the Event Store becomes challenging. To fetch an entity from the Data store, we need to process all the entity events. Also, sometimes we have different consistency and throughput requirements for reading and write operations.
In such use cases, we can use the CQRS pattern. In the CQRS pattern, the system’s data modification part (Command) is separated from the data read (Query) part. CQRS pattern has two forms: simple and advanced, which lead to some confusion among the software engineers.
In its simple form, distinct entity or ORM models are used for Reading and Write, as shown below:
It helps to enforce the Single Responsibility Principle and Separation of Concern, which lead to a cleaner design.
In its advanced form, different data stores are used for reading and write operations. The advanced CQRS is used with Event Sourcing. Depending on the use case, different types of Write Data Store and Read Data store are used. The Write Data Store is the “System of Records,” i.e., the entire system’s golden source.
For the Read-heavy applications or Microservice Architecture, OLTP database (any SQL or NoSQL database offering ACID transaction guarantee) or Distributed Messaging Platform is used as Write Store. For the Write-heavy applications (high write scalability and throughput), a horizontally write-scalable database is used (public cloud global Databases). The normalized data is saved in the Write Data Store.
NoSQL Database optimized for searching (e.g., Apache Solr, Elasticsearch) or reading (Key-Value data store, Document Data Store) is used as Read Store. In many cases, read-scalable SQL databases are used where SQL query is desired. The denormalized and optimized data is saved in the Read Store.
Data is copied from the Write store to the read store asynchronously. As a result, the Read Store lags the Write store and is Eventual Consistent.
- Faster reading of data in Event-driven Microservices.
- High availability of the data.
- Read and write systems can scale independently.
- Read data store is weakly consistent (eventual consistency)
- The overall complexity of the system increases. Cargo culting CQRS can significantly jeopardize the complete project.
When to use CQRS
- In highly scalable Microservice Architecture where event sourcing is used.
- In a complex domain model where reading data needs query into multiple Data Store.
- In systems where read and write operations have a different load.
When not to use CQRS
- In Microservice Architecture, where the volume of events is insignificant, taking the Event Store snapshot to compute the Entity state is a better choice.
- In systems where read and write operations have a similar load.
Enabling Technology Examples
Write Store: EventStoreDB, Apache Kafka, Confluent Cloud, AWS Kinesis, Azure Event Hub, GCP Pub/Sub, Azure Cosmos DB, MongoDB, Cassandra. Amazon DynamoDB
Read Store: Elastic Search, Solr, Cloud Spanner, Amazon Aurora, Azure Cosmos DB, Neo4j
Frameworks: Lagom, Akka, Spring, akkatecture, Axon, Eventuate