MESSAGING & EVENT-DRIVEN ARCHITECTURE / SYSTEM CONCEPT BRIEF

CQRS

CQRS (Command Query Responsibility Segregation) separates the model that changes data (commands) from the model that reads data (queries).

AdvancedPhase 06 / Topic 9 of 18RequirementsTrade-offsFailure modes
01

Overview

CQRS (Command Query Responsibility Segregation) separates the model that changes data (commands) from the model that reads data (queries). Writes go to a normalized store optimized for validation and consistency; reads come from one or more denormalized read models optimized for each screen or query, updated from events emitted by the write side.

CQRS lets reads and writes scale and evolve independently, and makes complex query needs (search, dashboards, feeds) simple without compromising the write model. The cost is eventual consistency between write and read models, plus more infrastructure to keep projections in sync.

A restaurant kitchen and its menu board

The kitchen (write side) follows strict recipes and inventory rules. The menu board and wait-time display (read side) are simplified views updated whenever the kitchen changes something. Customers read the board, not the kitchen's inventory ledger.

02

When to use it

  • Read and write workloads differ greatly in shape or scale.
  • Many different views of the same data (search, dashboards, mobile).
  • Complex domains with rich write-side business rules.
  • Combined with event sourcing.
03

Where it shows up in interviews

Specialized read models

Recognize it when: search, feeds, or dashboards over transactional data.

  • Design product search
  • Design an order history page at scale
High read/write asymmetry

Recognize it when: reads vastly outnumber writes.

  • Design a social feed
  • Design a banking statement view
04

Where it is used in real software

Search indexes

Many e-commerce systems write to PostgreSQL and project into Elasticsearch for search: a common CQRS form.

Axon Framework and EventStoreDB

Frameworks and databases built around CQRS and event sourcing.

Materialized views

Timelines and dashboards precomputed from event streams are read models.

05

Key terms

Command
Intent to change state (PlaceOrder).
Query
Request for data with no side effects.
Write model
Enforces invariants; source of truth.
Read model / projection
Denormalized view built from events.
Projection lag
Delay before reads reflect a write.
06

How it works, step by step

  1. 1
    Command arrives

    Validate against the write model's rules.

  2. 2
    Write model commits and emits events

    OrderPlaced, via outbox or event store.

  3. 3
    Projectors consume events

    Update read models: search index, dashboard table, cache.

  4. 4
    Queries hit read models

    Fast, shaped for each screen.

  5. 5
    Handle lag

    Optimistic UI or return data from the command response.

CQRS data flow
Step 1 / 4
Client
Command API
Write DB
Event stream
Projector
Read DB

STEP 1The client sends PlaceOrder. The command handler validates stock and rules.

07

One write model, several read models

E-commerce orders

Step 1 / 4
Read modelStoreShapeUsed by
Order historyDynamoDBOrders by customer, newest firstMobile app
Product searchElasticsearchProducts with stock and ratingsSearch page
Sales dashboardClickHouseRevenue by hour and regionAnalysts
Order detailsRedis cacheFull order JSONSupport tool

NOWRead model: Order history | Store: DynamoDB | Shape: Orders by customer, newest first | Used by: Mobile app

Each read model is disposable: it can be rebuilt by replaying events if its shape changes.

08

Implementation

// Command side: enforces rules, writes the source of truth, emits an eventexport async function placeOrder(cmd: { customerId: string; items: Item[] }) {  return db.transaction(async (tx) => {    await reserveStock(tx, cmd.items);                 // invariant: no overselling    const order = await tx.orders.insert({ customerId: cmd.customerId, items: cmd.items, status: "placed" });    await tx.outbox.insert({ type: "OrderPlaced", payload: order });    return order;  });} // Projector: builds a read model optimized for "my orders" screenexport async function onOrderPlaced(event: { payload: Order; eventId: string }) {  const { id, customerId, items, createdAt } = event.payload;  await readDb.put({    pk: `customer#${customerId}`,    sk: `order#${createdAt}#${id}`,    summary: { id, itemCount: items.length, total: items.reduce((s, i) => s + i.priceCents * i.qty, 0) },  }, { idempotencyKey: event.eventId });}
09

Complexity and performance

Read latencySingle lookup

Precomputed view.

Projection lagms to seconds

Monitor it.

10

Trade-offs

Performance and flexibility vs complexity

Tailored read models are fast and flexible, but you now maintain event pipelines and multiple stores.

Eventual consistency

Users may not see their change immediately in read views; design the UX for it.

11

Variants and related techniques

Same database CQRS

Separate read and write code paths and views within one database.

With event sourcing

Events are the write model; projections are built from the event store.

12

Common mistakes

  • Applying CQRS to simple CRUD.

    Fix: Use it only where read and write needs truly diverge.

  • Non-idempotent projectors.

    Fix: Events can be redelivered; dedupe by event ID or use upserts.

13

Interview questions

What problem does CQRS solve?

It separates the write model, which enforces business rules, from read models tailored to queries, so each can be optimized and scaled independently, avoiding one schema that is bad at both.

How do you handle a user not seeing their order right after placing it?

Return the created order in the command response and render it optimistically, route that user's read to the write model briefly, or wait until the projection reaches the event's position.

14

Practice problems

ProblemDifficultyWhat it trains
Split an order service into command and query sidesMediumProjections.
Design search and dashboards over an order systemHardMultiple read models.