Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CQRS

Overview

CQRS (Command Query Responsibility Segregation) splits a system’s data model into two distinct sides:

  • Command side (write): handles mutations — commands are optimized for correctness, validation, and writes.
  • Query side (read): handles reads — models are optimized for the exact queries the UI/API needs.

This is a specialization of CQS (Command-Query Separation) — the principle that a method either changes state or returns a value, never both — scaled up from method level to the data-model and service level.

graph LR
    CLI["Client"] --> CMD["Command API<br/>(POST /orders)"]
    CLI --> QRY["Query API<br/>(GET /orders?status=open)"]
    CMD --> WRITE["Write model<br/>(commands, aggregates)"]
    QRY --> READ["Read model<br/>(projections, denormalized)"]
    WRITE --> DB["OLTP store"]
    DB --> SYNC["Projection / sync"]
    SYNC --> READ

The Problem It Solves

In a classic CRUD service, one model serves both reads and writes. That works until reads and writes have conflicting requirements:

Writes wantReads want
Model shapeNormalized, validated, business rulesDenormalized, pre-joined, cached
ConsistencyStrong (transactions)Fast, eventually consistent OK
WorkloadLow volume, small payloadsHigh volume, fan-out queries
StorageOLTP (Postgres, MySQL)OLAP, search, cache, key-value

CQRS lets each side be independently shaped, scaled, and stored.

Without CQRS (CRUD) vs With CQRS

AspectCRUD single modelCQRS
ModelOne entity used for read + writeSeparate read/write models
Query shapeWhatever the write schema allowsExactly what the UI needs
ScalingScale both togetherScale each independently
ConsistencyStrong by defaultWrite-side strong; read-side eventual
ComplexityLowHigher (sync, two models)

Components

Write side (commands)

  • Commands are intent: PlaceOrder, CancelOrder, TransferFunds.
  • Validated against business rules by aggregates.
  • Writes go to an OLTP store, possibly via the transactional outbox for reliable publishing.

Read side (projections)

  • Projections consume write events (or DB changes) and build denormalized read models: materialized views, Elasticsearch indices, cache entries, star-schema tables.
  • Queries are simple, targeted, and fast — often just “look up by ID and return.”

Synchronization

graph TD
    WRITE["Write model"] -->|"events / CDC / outbox"| BUS["Message broker<br/>(Kafka, RabbitMQ, NATS)"]
    BUS --> P1["Projection: orders_by_customer"]
    BUS --> P2["Projection: analytics cube"]
    BUS --> P3["Projection: search index"]
    P1 --> Q1["Query API 1"]
    P2 --> Q2["Query API 2"]
    P3 --> Q3["Query API 3"]

Synchronization options: event stream (see Event Sourcing), change data capture (CDC) from the write DB, or dual-write (least safe). Dual-writes risk inconsistency if one write fails — prefer outbox + CDC.

Consistency

Read models are eventually consistent — after a write, there is a small lag before the projection updates. This is the fundamental trade-off: CQRS buys read flexibility and scale at the cost of read-your-writes guarantees. Mitigations:

  • Projection lag monitoring.
  • Read-your-writes via a hybrid path (write response contains the authoritative state).
  • Synchronous projection for the small subset of reads that demand it.

CQRS and Event Sourcing

CQRS does not require event sourcing and vice versa, but they compose well:

  • Event sourcing provides the event log; CQRS projections consume it.
  • The write side can be event-sourced (state rebuilt from events); the read side is inherently a projection.
  • See Event Sourcing.

When to Use CQRS

Strong fitPoor fit
Read-to-write ratio is extreme (many reads, few writes)Simple CRUD with one model
Same data read in very different shapes (dashboard + API + search)Team new to eventual consistency
High-read scalability needed independently of writesStrict read-your-writes everywhere
Complex domain with business rules on writesMVP / prototype speed matters

Many production systems use a partial CQRS: separate read models/tables for hot queries (e.g., a denormalized user_stats table or a search index) while keeping the canonical write model — without a full event-sourced write side.

Interview Questions

Q: What is the difference between CQS and CQRS?

CQS is a method-level principle (a method either queries or commands, never both — e.g., pop() violates it). CQRS applies the same idea at the architectural level: separate services/models for commands vs queries, often with different storage and consistency characteristics.

Q: Why can read models be eventually consistent?

Writes and reads are decoupled by a projection that copies data asynchronously. The window between a write and its appearance in the read model is small but nonzero; CQRS designs accept this in exchange for independently optimized, independently scaled read paths. Systems that need strict read-your-writes should keep those reads on the write model.

Q: How do you keep read models in sync without dual-write bugs?

Prefer transactional outbox + CDC: write the event/outbox row in the same DB transaction as the state change; a relay publishes it to the broker; projections consume and rebuild. Dual-writes (writing DB and broker separately) risk partial failure and divergence.

Q: When would you reject CQRS in an interview?

When the domain is simple, the read/write ratio is balanced, or the team can’t accept eventual consistency — CQRS adds operational complexity (two models, sync, lag) that is not justified. It’s a tool for a specific problem, not a default.

References

  • Martin Fowler, CQRS — https://martinfowler.com/bliki/CQRS.html
  • Greg Young, CQRS Documents — https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
  • Udi Dahan, Clarified CQRS — https://udidahan.com/2009/12/09/clarified-cqrs/
  • Fowler, Event Sourcing — https://martinfowler.com/eaaDev/EventSourcing.html
  • AWS Prescriptive Guidance: CQRS pattern — https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/cqrs-pattern.html