CQRS
also called Command Query Responsibility Segregation
Separating the model that accepts writes from the model that serves reads, so each can be optimised independently — at the cost of the gap between them.
Definition
Commands change state through one model; queries read from a different model, built from the first. In its full form the read side is a separate store shaped for the queries it serves, updated asynchronously.
CQRS is not event sourcing, and the frequent conflation of the two is why teams believe it is harder than it is. You can have CQRS with a plain relational write model and a search index as the read model. Most useful implementations are exactly that.
Why it matters
The write model and read model often want incompatible shapes. Writes want normalisation, constraints and transactional integrity. Reads want denormalised, precomputed, query-specific structures — often full-text or geospatial, often faceted, often ranked.
Trying to serve both from one schema means either slow queries or a write model bent out of shape to serve reads.
Industry example
A marketplace search problem shows the forcing function. An Airbnb-style listing has a transactional write model — a listing, its calendar, its price rules, its bookings — with hard invariants: two guests must not book the same night. That model is relational and constrained.
The read model is a completely different animal. A search for "2 guests, Lisbon, 12–15 June, under €150, with a kitchen" is a geospatial, faceted, availability-filtered, ranked query over millions of listings, and it must return in tens of milliseconds. No amount of indexing makes the transactional schema good at that.
So the read side becomes a separate index, populated asynchronously from changes on the write side. The consequence is an unavoidable and highly visible gap: a listing that was just booked may appear in search results for a few seconds. That is not a bug to be eliminated; it is the price of the architecture, and the product must be designed to handle it — by re-validating availability at booking time and telling the user honestly when the property has just gone.
This is the general shape of every dynamic-inventory marketplace: search is eventually consistent and booking is strongly consistent, with the authoritative check at the moment of commitment.
Implementation patterns
- Keep the write model authoritative. Any decision with a real consequence re-checks it.
- Rebuild the read model from scratch on demand. If the projection can be regenerated, a bug in it is an inconvenience rather than a disaster.
- Expose staleness. A timestamp or version on the read model so consumers can reason about it.
- Consider read-your-own-writes. After a user changes something, serve them from the write model or a session cache briefly, so their own change appears immediately. This removes most of the perceived weirdness at very low cost.
Failure scenarios
- The projection silently stops. Search results are hours stale and nobody is alerted. Lag on the projection needs the same monitoring as any service's error rate.
- The read model treated as authoritative by a workflow that should have checked the write model, so a booking is confirmed against stale availability.
- CQRS applied to simple CRUD, where one model would have served both perfectly and the team now maintains two plus a synchronisation mechanism.
Trade-offs
Gained: each side optimised and scaled independently, and read load removed from the write store. Sold: eventual consistency between them, a second store to operate, projection code to maintain and monitor, and a permanently more complex mental model. Apply it where the read and write shapes genuinely diverge — search, feeds, analytics-style views — and nowhere else.
Interview question
"Search shows a property as available; the booking then fails. Is that a bug? How would you design the interaction, and what would you show the user?"