# CQRS (Command Query Responsibility Segregation) Pattern

## Problem

A single model is used for both reading and writing data. As the application grows, read and write requirements diverge: reads need denormalized, fast-access projections while writes need normalized, validated domain models. Optimizing for one degrades the other. Scaling reads and writes independently becomes impossible.

## Solution

**CQRS** separates the application into two sides:

- **Command side** — Handles writes (create, update, delete). Uses a rich domain model optimized for enforcing business rules and invariants.
- **Query side** — Handles reads. Uses a denormalized read model optimized for fast queries and display.

Commands mutate state and return nothing (or just confirmation). Queries return data and cause no side effects. The two sides can use different data stores, different models, and scale independently.

## When to Use

- Read and write workloads have vastly different scaling requirements.
- The read model needs to be highly denormalized for performance.
- Complex business rules on the write side don't map well to query needs.
- Combined with Event Sourcing for a complete audit trail.

## When to Avoid

- Simple CRUD applications where reads and writes are balanced.
- When the added complexity of maintaining two models is not justified.
- Small teams that cannot maintain the synchronization infrastructure.

## Pseudocode

```
// Command side
CLASS CommandHandler:
    METHOD handle_create(cmd):
        entity = Domain.create(cmd.data)
        write_store.save(entity)
        event_bus.publish(EntityCreated(entity))

// Query side
CLASS QueryHandler:
    METHOD handle_get_list(query):
        RETURN read_store.fetch(query.filters)

CLASS ReadModelUpdater:
    ON EntityCreated(event):
        read_store.upsert(project(event.entity))
```

## Python

```python
from dataclasses import dataclass, field
from typing import Any


# --- Events ---
@dataclass
class Event:
    name: str
    data: dict[str, Any]


class EventBus:
    def __init__(self) -> None:
        self._handlers: dict[str, list] = {}

    def subscribe(self, event_name: str, handler) -> None:
        self._handlers.setdefault(event_name, []).append(handler)

    def publish(self, event: Event) -> None:
        print(f"  [EventBus] Publishing: {event.name}")
        for handler in self._handlers.get(event.name, []):
            handler(event)


# --- Write side (Command) ---
@dataclass
class Product:
    id: int
    name: str
    price: float
    stock: int


class WriteStore:
    def __init__(self) -> None:
        self._data: dict[int, Product] = {}

    def save(self, product: Product) -> None:
        self._data[product.id] = product

    def get(self, product_id: int) -> Product | None:
        return self._data.get(product_id)


class CommandHandler:
    def __init__(self, store: WriteStore, bus: EventBus) -> None:
        self._store = store
        self._bus = bus
        self._next_id = 1

    def create_product(self, name: str, price: float, stock: int) -> int:
        pid = self._next_id
        self._next_id += 1
        product = Product(pid, name, price, stock)
        self._store.save(product)
        print(f"  [Command] Created product #{pid}: {name}")
        self._bus.publish(Event("ProductCreated", {"id": pid, "name": name, "price": price, "stock": stock}))
        return pid

    def update_stock(self, product_id: int, delta: int) -> None:
        product = self._store.get(product_id)
        if product:
            product.stock += delta
            self._store.save(product)
            print(f"  [Command] Updated stock for #{product_id}: {product.stock}")
            self._bus.publish(Event("StockUpdated", {"id": product_id, "stock": product.stock}))


# --- Read side (Query) ---
class ReadStore:
    def __init__(self) -> None:
        self._catalog: list[dict[str, Any]] = []

    def upsert(self, item: dict[str, Any]) -> None:
        for i, existing in enumerate(self._catalog):
            if existing["id"] == item["id"]:
                self._catalog[i] = item
                return
        self._catalog.append(item)

    def get_catalog(self) -> list[dict[str, Any]]:
        return [item for item in self._catalog if item["stock"] > 0]

    def get_all(self) -> list[dict[str, Any]]:
        return list(self._catalog)


class ReadModelUpdater:
    def __init__(self, read_store: ReadStore) -> None:
        self._store = read_store

    def on_product_created(self, event: Event) -> None:
        d = event.data
        self._store.upsert({"id": d["id"], "name": d["name"], "price": d["price"], "stock": d["stock"]})
        print(f"    [ReadModel] Projected new product #{d['id']}")

    def on_stock_updated(self, event: Event) -> None:
        d = event.data
        for item in self._store.get_all():
            if item["id"] == d["id"]:
                item["stock"] = d["stock"]
                print(f"    [ReadModel] Updated stock for #{d['id']} -> {d['stock']}")
                return


class QueryHandler:
    def __init__(self, read_store: ReadStore) -> None:
        self._store = read_store

    def get_available_products(self) -> list[dict[str, Any]]:
        return self._store.get_catalog()


def main() -> None:
    bus = EventBus()
    write_store = WriteStore()
    read_store = ReadStore()
    updater = ReadModelUpdater(read_store)

    bus.subscribe("ProductCreated", updater.on_product_created)
    bus.subscribe("StockUpdated", updater.on_stock_updated)

    cmd = CommandHandler(write_store, bus)
    query = QueryHandler(read_store)

    print("--- Create products ---")
    cmd.create_product("Widget", 9.99, 100)
    cmd.create_product("Gadget", 24.99, 50)
    cmd.create_product("Doohickey", 4.99, 0)

    print("\n--- Query available products ---")
    for p in query.get_available_products():
        print(f"  {p['name']}: ${p['price']:.2f} (stock: {p['stock']})")

    print("\n--- Update stock ---")
    cmd.update_stock(3, 10)  # Doohickey now in stock

    print("\n--- Query available products ---")
    for p in query.get_available_products():
        print(f"  {p['name']}: ${p['price']:.2f} (stock: {p['stock']})")


if __name__ == "__main__":
    main()
```

**Output:**
```
--- Create products ---
  [Command] Created product #1: Widget
  [EventBus] Publishing: ProductCreated
    [ReadModel] Projected new product #1
  [Command] Created product #2: Gadget
  [EventBus] Publishing: ProductCreated
    [ReadModel] Projected new product #2
  [Command] Created product #3: Doohickey
  [EventBus] Publishing: ProductCreated
    [ReadModel] Projected new product #3

--- Query available products ---
  Widget: $9.99 (stock: 100)
  Gadget: $24.99 (stock: 50)

--- Update stock ---
  [Command] Updated stock for #3: 10
  [EventBus] Publishing: StockUpdated
    [ReadModel] Updated stock for #3 -> 10

--- Query available products ---
  Widget: $9.99 (stock: 100)
  Gadget: $24.99 (stock: 50)
  Doohickey: $4.99 (stock: 10)
```

## JavaScript

```javascript
// --- Event Bus ---
class EventBus {
  constructor() {
    this._handlers = {};
  }

  subscribe(eventName, handler) {
    if (!this._handlers[eventName]) this._handlers[eventName] = [];
    this._handlers[eventName].push(handler);
  }

  publish(event) {
    console.log(`  [EventBus] Publishing: ${event.name}`);
    for (const handler of this._handlers[event.name] || []) {
      handler(event);
    }
  }
}

// --- Write side ---
class WriteStore {
  constructor() {
    this._data = new Map();
  }
  save(product) {
    this._data.set(product.id, { ...product });
  }
  get(id) {
    return this._data.get(id) || null;
  }
}

class CommandHandler {
  constructor(store, bus) {
    this._store = store;
    this._bus = bus;
    this._nextId = 1;
  }

  createProduct(name, price, stock) {
    const id = this._nextId++;
    const product = { id, name, price, stock };
    this._store.save(product);
    console.log(`  [Command] Created product #${id}: ${name}`);
    this._bus.publish({ name: "ProductCreated", data: product });
    return id;
  }

  updateStock(productId, delta) {
    const product = this._store.get(productId);
    if (product) {
      product.stock += delta;
      this._store.save(product);
      console.log(`  [Command] Updated stock for #${productId}: ${product.stock}`);
      this._bus.publish({ name: "StockUpdated", data: { id: productId, stock: product.stock } });
    }
  }
}

// --- Read side ---
class ReadStore {
  constructor() {
    this._catalog = [];
  }

  upsert(item) {
    const idx = this._catalog.findIndex((x) => x.id === item.id);
    if (idx >= 0) this._catalog[idx] = item;
    else this._catalog.push(item);
  }

  getCatalog() {
    return this._catalog.filter((x) => x.stock > 0);
  }

  getAll() {
    return this._catalog;
  }
}

class ReadModelUpdater {
  constructor(readStore) {
    this._store = readStore;
  }

  onProductCreated(event) {
    const d = event.data;
    this._store.upsert({ id: d.id, name: d.name, price: d.price, stock: d.stock });
    console.log(`    [ReadModel] Projected new product #${d.id}`);
  }

  onStockUpdated(event) {
    const d = event.data;
    const item = this._store.getAll().find((x) => x.id === d.id);
    if (item) {
      item.stock = d.stock;
      console.log(`    [ReadModel] Updated stock for #${d.id} -> ${d.stock}`);
    }
  }
}

class QueryHandler {
  constructor(readStore) {
    this._store = readStore;
  }
  getAvailableProducts() {
    return this._store.getCatalog();
  }
}

// --- Main ---
const bus = new EventBus();
const writeStore = new WriteStore();
const readStore = new ReadStore();
const updater = new ReadModelUpdater(readStore);

bus.subscribe("ProductCreated", (e) => updater.onProductCreated(e));
bus.subscribe("StockUpdated", (e) => updater.onStockUpdated(e));

const cmd = new CommandHandler(writeStore, bus);
const query = new QueryHandler(readStore);

console.log("--- Create products ---");
cmd.createProduct("Widget", 9.99, 100);
cmd.createProduct("Gadget", 24.99, 50);
cmd.createProduct("Doohickey", 4.99, 0);

console.log("\n--- Query available products ---");
for (const p of query.getAvailableProducts()) {
  console.log(`  ${p.name}: $${p.price.toFixed(2)} (stock: ${p.stock})`);
}

console.log("\n--- Update stock ---");
cmd.updateStock(3, 10);

console.log("\n--- Query available products ---");
for (const p of query.getAvailableProducts()) {
  console.log(`  ${p.name}: $${p.price.toFixed(2)} (stock: ${p.stock})`);
}
```

**Output:**
```
--- Create products ---
  [Command] Created product #1: Widget
  [EventBus] Publishing: ProductCreated
    [ReadModel] Projected new product #1
  [Command] Created product #2: Gadget
  [EventBus] Publishing: ProductCreated
    [ReadModel] Projected new product #2
  [Command] Created product #3: Doohickey
  [EventBus] Publishing: ProductCreated
    [ReadModel] Projected new product #3

--- Query available products ---
  Widget: $9.99 (stock: 100)
  Gadget: $24.99 (stock: 50)

--- Update stock ---
  [Command] Updated stock for #3: 10
  [EventBus] Publishing: StockUpdated
    [ReadModel] Updated stock for #3 -> 10

--- Query available products ---
  Widget: $9.99 (stock: 100)
  Gadget: $24.99 (stock: 50)
  Doohickey: $4.99 (stock: 10)
```

## Related Patterns

- **Event Sourcing** — CQRS write side often uses Event Sourcing as its persistence strategy; events feed the read side projections.
- **Repository** — Both sides use Repositories, but for different models: one command Repository (rich domain), one query Repository (flat read model).
- **Mediator** — Commands and Queries are often routed through a Mediator (e.g., MediatR, command bus) to their handlers.
- **Observer** — After a command is handled, domain events are emitted via Observer, which update the read side.
- **Service Layer** — CQRS command handlers and query handlers are the service layer in CQRS-based architectures.
