Cloud · Messaging · DWG NO. 032

CQRS

Separate the model used to write data from the model used to read it, so each can be optimized independently.

TypeMessaging / Data
ScopeOne bounded context
ComplexityMedium–High
Common inHigh-read systems, reporting dashboards, event-driven services

One model, pulled in two directions

A single data model has to serve both writes (validate an order, enforce business rules, keep invariants) and reads (render a dashboard joining five tables, paginate a feed). Optimizing the schema for one usually makes the other slower or more awkward — normalized for safe writes fights against denormalized for fast reads.

A write model and a read model, kept in sync

Commands (writes) go through a model focused on validation and consistency. Queries (reads) go through a separate model — often a denormalized, pre-joined view — built specifically for how the data is displayed. The read model is updated asynchronously after each write, so it's typically eventually consistent.

CommandWrite Modelnormalizedsync/async updateRead ModeldenormalizedQuery

A tiny CQRS split: write model vs. a projected read model

`placeOrder` only touches the write model and enforces the rule. The read model is a separately shaped projection, rebuilt from write events.

cqrs-order.js
// Write side: enforces invariants, source of truth
const writeModel = { orders: new Map() };

function placeOrder(id, items) {
  if (items.length === 0) throw new Error("order must have at least one item");
  writeModel.orders.set(id, { id, items, status: "placed" });
  projectToReadModel(id); // keep the read side up to date
}

// Read side: denormalized, shaped for a dashboard
const readModel = { orderSummaries: new Map() };

function projectToReadModel(id) {
  const order = writeModel.orders.get(id);
  readModel.orderSummaries.set(id, {
    id: order.id,
    itemCount: order.items.length,
    status: order.status,
  });
}

function getOrderSummary(id) {
  return readModel.orderSummaries.get(id); // fast, pre-shaped for display
}

placeOrder("order-1", ["book", "pen"]);
console.log(getOrderSummary("order-1")); // { id: 'order-1', itemCount: 2, status: 'placed' }

Trade-offs