Skip to content

Week 10 — Event Sourcing, CQRS & Microservices

Instead of storing current state, store every event that led to it.

# Traditional (state storage)
{ order_id: 123, state: 'done', amount: 5000, updated_at: '...' }
# Event sourcing (event log)
OrderCreated { order_id: 123, customer_id: 456 }
LineAdded { product_id: 789, qty: 2, price: 1000 }
PriceUpdated { line_id: 1, new_price: 1500 }
OrderConfirmed { confirmed_at: '2025-01-15T10:00:00' }
PaymentReceived { amount: 5000 }

Replay events → reconstruct current state at any point in time.

  • Audit trail by default — every change is recorded with who and when
  • Temporal queries — “what was this order worth at 10:00 AM on Jan 15?”
  • New projections — build new read models from existing events without migrations
  • Debug — replay events to reproduce a bug exactly
  • Replaying thousands of events is slow → use snapshots (checkpoint current state every N events)
  • Schema evolution: old events must be readable forever → use upcasting to transform old event formats
  • Direct querying is hard → use CQRS

CQRS — Command Query Responsibility Segregation

Section titled “CQRS — Command Query Responsibility Segregation”

Separate the write model (commands) from the read model (queries).

Write path: Command → Validate → Apply → Event store (PostgreSQL)
↓ (async)
Read path: Query → Read model (denormalised, optimised view)

You don’t need full event sourcing for CQRS. A practical starting point:

  • PostgreSQL for writes (normalised, ACID)
  • Materialised views or Elasticsearch for reads (denormalised, fast)
  • Async sync between them
  • Independent deployment — deploy the payment service without touching the warehouse service
  • Decoupled scaling — scale only the bottleneck
  • Team autonomy — teams own their services end-to-end
  • Network latency — function call: ~100 ns → network call: ~1 ms (10,000× slower)
  • Distributed transactions → sagas required
  • Operational overhead → N services to monitor, deploy, debug

A well-structured monolith with clean module boundaries often beats microservices for teams under ~50-100 engineers.

ERP systems (Odoo, SAP) are monoliths by design. The tight coupling between accounting, inventory, and sales is a feature — it enables atomic transactions across modules.

Gradually extract services from a monolith without a big-bang rewrite:

All traffic → Monolith
↘ route /payments/* → new Payment Service

The monolith “strangles” as more routes move to new services. Risk is managed incrementally.

  • If you use event sourcing, what happens when your event schema changes 2 years later?
  • When does CQRS without event sourcing make sense?
  • Name 3 things that are harder in microservices than in a monolith.