CQRS
- Pradeep P
- 3 days ago
- 3 min read
Series: Modern System Design · Layer 4 — Data
Layer 4 · Post 9 of 11
← Previous: Sagas → Next: Event Sourcing
Layer 4 — Data · Post 48 of 88
CQRS splits the write model from the read model so each can scale and evolve independently. The cost is extra moving parts and eventual views.
What you'll learn
Commands versus queries as two models, not two REST verbs
Why the read side is eventually consistent — and when a replica already is "mild CQRS"
When CQRS is overkill, and how it pairs with sagas and event sourcing (next)
The idea in one minute
CQRS (Command Query Responsibility Segregation) means you do not use the same model to change state and to show state.
Commands (place order, cancel booking) hit a write model shaped for invariants: normalized tables, aggregates, strict transactions.
Queries (order history, search, dashboards) hit a read model shaped for screens: denormalized rows, Elasticsearch, Redis, a column store.
Command --> [ Write DB / aggregate ] | +-- events / CDC / projection --> [ Read DB / search / cache ] Query ----------------------------------------->
The projection is behind. That is eventual consistency (post 44) applied to your own UI.
Why it matters
After sagas, your write path is already a state machine. Listing "my trips" should not join six service databases on every GET. CQRS lets the write side stay correct (ACID locally, saga globally) while the read side stays fast and independently scaled.
Interviewers want you to not apply CQRS to a CRUD admin app with 200 requests per minute. They also want you to notice you already did a baby version if you read from a replica and write to a primary.
How it works
A command is validated against the write model (optimistic version, locks, one aggregate).
The write commits. That commit emits something: a domain event, an outbox row, or a WAL change (CDC).
A projector updates one or more read stores. It must handle retries (idempotent projections).
Queries never call the write DB — or they do only for "read-your-writes" after a command (read the primary, or wait for a version).
You can CQRS without event sourcing: the write model is still current-state tables; you just fan out to a read schema. Event sourcing (next) makes the write model itself a log. People bundle them; they are not the same.
Sync options: same transaction as an extra table (simple, coupled), async messaging (scale, lag), CDC (no dual write).
A simple example
Shopify-style shop: checkout commands go to an orders service (Postgres, saga with payments). The merchant dashboard is Elasticsearch plus a order_summaries table updated from OrderPlaced events. A customer who just paid might poll or subscribe until the read model shows "Paid" — or you return the write-side status on the confirmation page (read-your-writes) and let the dashboard lag.
Common mistakes
CQRS as a default folder structure. Two models, two deploys, two failure modes, for a to-do list.
Queries that sneak back to the write DB "just this once." You have not segregated anything; you have a leaky abstraction.
No lag story. Support tools must see fresh data. Give them the write store or a "rebuild this user" button.
Treating the read model as source of truth. Refunds and audits go through commands, not UPDATE on the projection.
How this shows up in real systems
Read replicas: CQRS-lite — same schema, different load.
Elasticsearch / OpenSearch / Algolia next to Postgres: search is a read model.
DynamoDB + Elastic: common AWS diagram.
Kafka projections: ksqlDB, consumer services building Redis views.
EventStoreDB / Axon: CQRS + event sourcing as a package.
Recap
CQRS: write for correctness, read for shape and scale. The view lags unless you pay to hide it.
Do not buy the full pattern until one model is clearly in the way.
If the write model is a log of events, that is event sourcing — next.
Series: Modern System Design · Layer 4 — Data
Layer 4 · Post 9 of 11
← Previous: Sagas → Next: Event Sourcing



Comments