The Architecture Reference

Ddd tactical · Domain-Driven Design · Advanced

CQRS

Command-Query Responsibility Segregation — one strongly-consistent command model and any number of read-only projections.

Ddd tactical Advanced ⏱ 4 min read Complete

🧭 Analogy

A library keeps one authoritative catalog of every book it owns (the source of truth) but also prints purpose-built lists: new arrivals, books by genre, a staff-picks shelf. Those lists are read-only views generated from the catalog — wipe them and you can reprint from the catalog at any time. That’s CQRS.

Multiple models of the same data

CQRS (Command-Query Responsibility Segregation) shares ports-and-adapters’ principles but represents the system’s data in multiple persistent models, segregated by responsibility. It solves two problems: working with the same data in several models or database kinds, and overcoming an event-sourced model’s limited querying.

graph TD
C["Command"] --> CM["Command execution model<br/>(strongly consistent, source of truth)"]
CM --> Store[("OLTP / event store")]
Store --> Eng["Projection engine"]
Eng --> R1["Read model: SQL view"]
Eng --> R2["Read model: search index"]
Eng --> R3["Read model: in-memory cache"]
Q["Query"] --> R1
Q --> R2
  • Command execution model — The single model for state-modifying operations; the only strongly consistent model and the source of truth. It supports reading strongly-consistent state and optimistic concurrency.
  • Read models / projections — As many read-only models as needed: a precached projection in a durable DB, a flat file, or an in-memory cache. They are wipeable and regenerable, and no operation modifies them directly — they’re like materialized views.
  • Polyglot persistence — Use multiple models and database kinds because no single one serves all needs.

Projecting the read models

  • Synchronous projection (catch-up subscription) — The engine (1) queries the command store for records changed after the last checkpoint, (2) updates read models, (3) stores the new checkpoint. Correctness rule: checkpoint queries must return consistent results — no later record may appear with a value below an already-returned checkpoint. Big advantage: adding or rebuilding a projection is trivial — reset the checkpoint to 0.
  • Asynchronous projection — The command model publishes committed changes to a message bus; projection engines subscribe. It scales well but is prone to out-of-order and duplicate messages, which can corrupt read models.
graph LR
Eng["Projection engine"] -->|"1. read changes<br/>after checkpoint"| Store[("Command store")]
Eng -->|"2. update"| RM["Read model"]
Eng -->|"3. save new checkpoint"| CP["Checkpoint"]
CP -.->|"reset to 0 = rebuild"| Eng

Recommended strategy

Always implement synchronous projection first, then optionally add asynchronous on top. The synchronous catch-up subscription is correct and rebuildable; async buys scale but inherits the hazards of unordered, duplicated messages.

Commands can return data

A persistent CQRS myth says commands may not return data, forcing callers to always poll a read model. Reject it: a command should tell the caller success or failure and why, and can and often should return data — but that data must come from the strongly-consistent command model, not an eventually-consistent read model.

When to use it

CQRS fits apps that need the same data in multiple models or databases, and it is obligatory for event-sourced domain models — the event store is built for appending and replaying, not querying, so projections give you the queryable views. As a heuristic, derive the architecture from the business-logic pattern: event-sourced → CQRS; but CQRS benefits any pattern that needs data in multiple persistent models.

Read models are disposable

Because read models are pure projections of the source of truth, treat them as cache: you can change a read model’s schema by deleting it and replaying. This is what makes adding new query shapes cheap, long after the system has shipped.

See also

When to use it — and when not

✅ Reach for it when

  • When you need the same data represented in multiple persistent models or database kinds (polyglot persistence).
  • Obligatory for event-sourced domain models, whose event store is poor at querying.
  • When you want to add or rebuild read models cheaply and independently.

⛔ Think twice when

  • When a single model serves all needs — CQRS adds eventual consistency and machinery you may not need.
  • Don't assume commands may not return data — they can and often should.

Check your understanding

Score: 0 / 4

1. In CQRS, which model is strongly consistent?

Only the command model is strongly consistent and the source of truth; read models are eventually consistent, read-only, and rebuildable projections.

2. A common CQRS misconception to reject is...?

A command should tell the caller success or failure and why, and can and often should return data — but that data must come from the strongly-consistent command model.

3. What does a synchronous projection use to track progress?

A synchronous projection engine queries the command store for records changed after the last checkpoint, updates read models, and stores the new checkpoint — a catch-up subscription.

4. What is the recommended projection strategy?

Asynchronous projections scale but are prone to out-of-order and duplicate messages; always implement the synchronous catch-up projection first, then optionally add async.

Comments

Sign in with GitHub to join the discussion.