Advanced14 min

CQRS

Separate the write model from the read model when their needs diverge.

By the end of this lesson, decide when splitting reads from writes is worth the added complexity.

How deep?
How the pieces actually move.

CQRS (Command Query Responsibility Segregation) splits the model that handles writes (commands) from the model(s) that serve reads (queries). Writes go to a normalized, consistency-focused store; reads are served from separate, denormalized read models shaped for exactly how they're queried.

First, the whole system

First, see the whole system. Then we’ll open it up.

Write model, read model(s)

Write model, read model(s)

  1. Commands update the write model; its events build projections into multiple read models optimized for specific queries; queries hit the read models.
flowchart LR
  cmd[Commands] --> wm[Write model / source of truth]
  wm -->|events| proj[Projections]
  proj --> rm1[Read model: search]
  proj --> rm2[Read model: dashboard]
  q[Queries] --> rm1
  q --> rm2

Read models are usually updated asynchronously from the write side's events (often via the outbox), so they are eventually consistent — a read right after a write may not reflect it. CQRS pairs naturally with event sourcing but doesn't require it. Use it where read and write loads or shapes differ sharply; avoid it for simple CRUD where it's pure overhead.

Consistency

CQRS lets you scale and shape reads independently of writes, but introduces eventual consistency between them and the operational cost of projections. For most CRUD, a single model is simpler and correct; CQRS earns its keep under divergent, high-scale read/write demands.

Why might a user not see their own change immediately under CQRS?

Read models update asynchronously from write events, so they're eventually consistent; the projection may lag the write.

CQRS read models are often built from events. How do teams migrate a big legacy system toward this kind of architecture safely?

Next: Strangler Fig →