CQRS: Separating Write Models from Read Projections
CQRS is not about code organization — it is about materializing read-optimized projections from an authoritative write model, accepting an eventual consistency window in exchange for independently scalable read and write throughputs. Learn aggregate commands, optimistic concurrency, projection lag, and when the CQRS tax is worth paying.
Distributed Architecture & System Design
CQRS: Separating Write Models from Read Projections
Every boundary is a failure isolation decision — and one of the most consequential boundaries in a distributed system is the boundary between the model you write to and the model you read from. The most common mistake teams make with CQRS is treating it as a code organization pattern: they create a CommandService and a QueryService class, route reads to one and writes to the other, and call it CQRS. It isn't. True CQRS materializes read-optimized projections from an authoritative write model, accepts an explicit eventual consistency window between them, and scales each path independently. The added complexity is significant — and is only justified when read and write requirements genuinely diverge.
Series positioning: This is Part 5 of the Distributed Architecture & System Design series. Having established inter-service communication and messaging brokers in Parts 1–4, it introduces Command-Query Responsibility Segregation (CQRS), separating write-side transactional aggregates from read projections before exploring event-sourced persistence in Part 6: Event Sourcing with Kafka.
1. Why Shared Models Break at Scale
A system with a single normalized database model shared by both reads and writes faces a fundamental tension:
In a high-read system, query load contends with write locks. In a high-write system, write load floods the read connection pool. A normalized schema can be optimized for one workload — not both simultaneously. CQRS solves this by materializing a denormalized read model optimized for the query shape, populated asynchronously from the write model.
2. The CQRS Model Separation

2.1 The Write Side: Aggregates & Commands
The write model enforces all business invariants. Commands mutate aggregate state, and any mutation that violates an invariant is rejected before it reaches the database:
2.2 Optimistic Concurrency Control
The write model must prevent lost updates under concurrent modification without pessimistic write locks on the read model:
3. The Read Side: Projections
The read side is a projection — a materialized, denormalized view of data derived from the event stream. Its schema is optimized for the query shape, not for normalization.
3.1 Catch-Up Subscriptions
3.2 Projection Rebuilds
Because the event log is the source of truth, any projection can be rebuilt at any time:
Projection schemas are disposable. Because the full event log is the source of truth, adding a new field to a projection (e.g., categories_purchased) requires replaying the log once — not migrating rows in a production table with live traffic. This is one of the most powerful operational properties of CQRS + Event Sourcing.
4. The Eventual Consistency Window
The lag between a write committing and its read projection updating is not a bug — it is a mandatory, explicit property of any asynchronous CQRS system.

4.1 UX Patterns for Eventual Consistency
| Scenario | Pattern |
|---|---|
| User places order, immediately views dashboard | Return 202 + optimistic UI update on client; show "processing" state until next poll confirms |
| Admin queries order count after bulk import | Add "last updated" timestamp to projection; display staleness explicitly |
| Financial report requires strong consistency | Query write store directly for the specific time window — bypass the projection |
5. When CQRS Is and Is Not Appropriate
5.1 Apply CQRS When
| Signal | Explanation |
|---|---|
| Read/write traffic ratio > 10:1 | Read path needs independent scaling and different optimizations |
| Query shapes are incompatible with normalized schema | Dashboard requires 6-table JOIN; each query serves a different denormalized shape |
| Event-driven audit trail required | All state changes must be recorded as business events for compliance |
| Multiple read models needed | Same data accessed via different projections (search, reporting, API) |
5.2 Do NOT Apply CQRS When
| Signal | Explanation |
|---|---|
| Small team (< 5 engineers) | Operational overhead exceeds benefit |
| Simple CRUD domain | No complex invariants to enforce; no projection divergence |
| Strong consistency required everywhere | Eventual consistency window is not acceptable for the business |
| Domain model is still evolving rapidly | Premature event schema locks in wrong assumptions |
Summary
| Architectural Concern | Production Rule |
|---|---|
| Write Model Invariants | Enforces invariants and emits domain events; optimized exclusively for state transitions, never used for reporting queries. |
| Read Model Projections | Denormalized projections optimized for query shape; eventually consistent with write model. |
| Eventual Consistency Window | The lag between write commit and projection materialization is explicit; UX must account for processing states. |
| Disposable Projections | Projection schemas are disposable; replay the event log from offset 0 to rebuild or migrate read stores. |
| Optimistic Concurrency | expected_version checks on write prevent lost updates without requiring pessimistic locks. |
What's Next
Now that we have separated write models from read projections, Part 6: Event Sourcing with Kafka takes CQRS to its architectural conclusion: persisting domain state as an append-only sequence of immutable events with snapshot optimizations and Avro schema evolution.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.