Architecture patterns
CQRS and read models
Command Query Responsibility Segregation uses separate models for writing and reading. Learn how read models are built from events, the eventual consistency they bring, and how to hide that lag from users.
CQRS means using different models for changing data and for reading it. The write side enforces the rules and accepts commands. The read side is one or more materialized views, each shaped for a specific query and updated from the events the write side produces.
The big idea
CQRS doesn't require event sourcing, and event sourcing doesn't strictly require CQRS. In practice, though, event-sourced systems almost always need it, because the event stream can't answer queries. A simple CQRS system can be a Postgres write model, with CDC feeding an Elasticsearch index for search and a denormalized table for the dashboard.
The defining trade-off is eventual consistency on reads. After a user places an order, the search projection may take 200 ms to 2 seconds to show it. A user who places an order and immediately sees "no orders found" will file a bug.
Hiding the lag
Three standard mitigations:
- Read-your-writes. After a command, return the new state in the response, or have the client read from the write model for the next few seconds.
- Version tokens. The command returns the position in the event log that it produced. The client passes that token to the query, and the read model waits until it has processed that position, or tells the client it is still catching up.
- Optimistic UI. Show the change on the client straight away, and reconcile it when the server answers.
Use CQRS when the read and write shapes really differ. For example, the write model is normalized to protect invariants, while reads need full-text search, aggregates or a different partitioning. Don't add it to a CRUD screen where one table serves both well.
Read models are derived data. If one is wrong, you can delete it and rebuild it by replaying the log. That is a rebuild, not a restore.
Common mistakes
- Adding CQRS to a simple screen. Two models to maintain, and nothing gained.
- Showing read-model data straight after a write. Use one of the three mitigations above.
- Treating a read model as precious. It is a cache of the log, so keep the log and rebuild freely.
Check yourself
1. A user places an order, and the search page doesn't show it yet. What do you do? Read from the write model right after the command, or pass a version token so the read waits until the projection catches up.
2. Why can a read model be deleted safely? It is derived from the event log. You can rebuild it by replaying the events.
Key takeaways
- CQRS separates the write model, which protects invariants, from read models, which are shaped for queries.
- Read models are built from events and are eventually consistent.
- Hide the lag with read-your-writes, version tokens or optimistic UI.
- Read models are disposable. Rebuild them from the log.
Further learning
Greg Young, who coined CQRS, explains it and event sourcing from first principles, including why the two fit together.
Next, see how these pieces fail in practice in pitfalls of event-driven systems.
Related
Event sourcing
Store the events as the source of truth and derive the current state by replaying them. Learn the gains in audit and replay, and the costs of snapshots, schema changes and querying.
Change data capture
CDC turns committed database changes into events by reading the database's own replication log, which avoids dual writes. Learn how it works, and why an outbox table usually sits in front of it.
Event-driven architecture, CQRS and event sourcing
Building systems around events, from change data capture to event sourcing and read models, and knowing when it simplifies a system versus creating a distributed mess.