Week 10 — Event Sourcing, CQRS & Microservices
Event Sourcing
Section titled “Event Sourcing”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.
Benefits
Section titled “Benefits”- 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
Trade-offs
Section titled “Trade-offs”- 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
Microservices
Section titled “Microservices”What They Solve
Section titled “What They Solve”- 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
What They Cost
Section titled “What They Cost”- 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
When Monolith Wins
Section titled “When Monolith Wins”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.
Strangler Fig Pattern
Section titled “Strangler Fig Pattern”Gradually extract services from a monolith without a big-bang rewrite:
All traffic → Monolith ↘ route /payments/* → new Payment ServiceThe monolith “strangles” as more routes move to new services. Risk is managed incrementally.
Self-check
Section titled “Self-check”- 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.