CQRS
Separate the write model from the read model when their needs diverge.
By the end of this lesson, decide when splitting reads from writes is worth the added complexity.
CQRS (Command Query Responsibility Segregation) splits the model that handles writes (commands) from the model(s) that serve reads (queries). Writes go to a normalized, consistency-focused store; reads are served from separate, denormalized read models shaped for exactly how they're queried.
First, see the whole system. Then we’ll open it up.
Write model, read model(s)
- Commands update the write model; its events build projections into multiple read models optimized for specific queries; queries hit the read models.
flowchart LR cmd[Commands] --> wm[Write model / source of truth] wm -->|events| proj[Projections] proj --> rm1[Read model: search] proj --> rm2[Read model: dashboard] q[Queries] --> rm1 q --> rm2
Read models are usually updated asynchronously from the write side's events (often via the outbox), so they are eventually consistent — a read right after a write may not reflect it. CQRS pairs naturally with event sourcing but doesn't require it. Use it where read and write loads or shapes differ sharply; avoid it for simple CRUD where it's pure overhead.
CQRS lets you scale and shape reads independently of writes, but introduces eventual consistency between them and the operational cost of projections. For most CRUD, a single model is simpler and correct; CQRS earns its keep under divergent, high-scale read/write demands.
Why might a user not see their own change immediately under CQRS?
Read models update asynchronously from write events, so they're eventually consistent; the projection may lag the write.