Separating the read model from the write model
A Socratic walk-through of separating the read model from the write model — reasoned out one step at a time, not lectured.
The question we started with
THE QUESTION #Why describe the same information twice, once for changing it and once for reading it?
Most systems are built around one description of each thing. An Order class, an orders table, one shape used by the code that places an order and by the code that lists last month's orders on a dashboard.
That single description feels like a virtue. One definition, no drift, no duplication. Yet mature systems very often end up with two — one shape for changing, another for reading — and the second is not a cache of the first. Before deciding whether that is discipline or duplication, it is worth asking what the single shape was actually being asked to do.
Reasoning it through
REASONING #Take the two jobs separately.
Placing an order has to decide whether it is allowed. Is the item in stock? Is the card valid? Is this within the customer's limit? To decide, the model needs exactly the data the rules depend on, and it needs that data to be current and consistent, because a decision made on stale facts may be wrong. Notice what it does not need: the customer's display name, the product's photograph, last month's totals. None of that participates in any rule.
Displaying a dashboard has to present. It needs names, images, totals, and a rolled-up view across many orders — and it needs almost none of it to be exactly current. A dashboard showing the state as of two seconds ago is a good dashboard.
Now hold the two lists side by side. They barely overlap. One wants a small, tightly guarded set of facts, strictly consistent. The other wants a wide, joined, denormalized set, tolerant of lag. Ask yourself what a single model serving both must look like.
It must carry every field either side needs, so it grows — an object with fifty fields where the rules use six. It must be loaded in full to change one thing, so writes are heavier than the decision requires. And it must be shaped by whichever side complains loudest, so the presentation fields creep into the thing that enforces the rules, and the rules become harder to see among them.
That is the actual finding, and it is stronger than "reads and writes have different performance needs". They have different information needs, and a model is a description of what information matters. Forcing one description to answer both questions makes it a poor description of either.
Separating them lets each be honest. The write model holds only what the rules consult, so its invariants are visible and enforceable. The read model is shaped per question — often one stored view per screen, pre-joined and flattened, needing no interpretation at query time.
And the join between them is a propagation: the write side, having accepted a change, publishes it; the read side updates its views. Which introduces the cost, and it must be stated plainly rather than buried. The read model is eventually consistent. A user who submits a change and is immediately shown a list may see the old value. That is not an edge case to be engineered away; it is the defining property of the arrangement, and it either has to be designed for in the interface — showing the pending change optimistically, or acknowledging the write explicitly — or the arrangement is wrong for that screen.
Two clarifications about scope, since this pattern is more often misapplied than misunderstood. First, it does not require two databases. Two mapped shapes over one database is a legitimate and much cheaper form of it, and captures most of the modelling benefit without any lag. Second, it does not require event sourcing. The two pair naturally — an event log is an excellent thing to build projections from — but each stands alone, and Greg Young, who named CQRS, has spent years saying so precisely because the two are so often conflated.
The honest summary of when to use it: apply it where the asymmetry is real and painful — a complex rule-bearing write side, or a read side under load with quite different shape needs. Applying it uniformly to every part of a system, most of which is simple create-read-update-delete work, adds two models and a synchronisation problem to code that had neither.
The analogy
THE ANALOGY #Think of a hospital's admissions desk and its ward whiteboard. The desk works from a strict, minimal record — identity, allergies, consent — because those are what its decisions turn on, and being wrong there is dangerous. The whiteboard shows bed, name, consultant and status at a glance, because its job is to be read across the room in one second. Nobody proposes running admissions off the whiteboard, and nobody wants to read the admissions file to find a free bed.
A whiteboard is updated by the same hand that files the record, whereas a read model is updated by a separate mechanism after the fact — so the whiteboard can be briefly wrong in a way the ward has no way of noticing.
Clarifying the model
THE MODEL #The misconception worth correcting head-on is that this is a performance pattern — that the read side exists because the write side is too slow. Read it that way and you conclude it is premature optimisation for most systems, and you miss the point: the primary benefit is that the write model gets to be small, containing only what its rules need, which is what makes those rules legible and enforceable. The read-side speed is a consequence.
The second is that "two models" implies duplicated logic. It does not, because the two hold different kinds of content. The write model holds rules; the read model holds no rules at all, only the shape of an answer. If you find yourself implementing a business rule in a read model, that is the signal that the boundary has been drawn in the wrong place — not that the pattern is failing.
A picture of it
THE PICTURE #How to readStart at the input at the top and take the branch that matches the intent. The left path is the only one that can change anything, and it must pass the invariant gate before reaching the authoritative store; the right path never touches rules and reads a prepared view. The two meet only through the projection updater, and the dotted edge to the shaded node is the cost of that indirection — the window in which a reader sees the state from just before their own write.
What became clearer
WHAT CLEARED #Deciding and displaying need different information, not just different speed, so a single model asked to serve both is a compromised description of each. Splitting them lets the write side shrink to its rules and the read side flatten to its questions — at the fixed, unavoidable price of a propagation delay that must be designed for in the interface rather than wished away.
Where to go next
ONWARD #- Designing interfaces that tolerate eventual consistency, including optimistic rendering.
- How this pairs with event sourcing, and why neither requires the other.
- Rebuilding projections from scratch, and why that ability is the test of a healthy split.
Key terms
TERMS #| Term | What it means |
|---|---|
| CQRS | command query responsibility segregation: separate models for changing state and for reading it. |
| Command | a request to change something, which the write model may reject. |
| Projection | a derived read-side view, shaped for a particular question and rebuilt from the write side. |
| Eventual consistency | the guarantee that the read side converges on the write side, but not immediately. |
Every term the collection defines is gathered in the glossary.