Event Sourcing & CQRS Architecture
The Banking Ledger That Took 3 Days to Replay
Test your architecture intuition: Pitch a 7-axis solution, survive two aggressive reviewer objections, and inspect the staff-level Teacher Gold Answer.
1. What It Is & Why It Exists
The Core Problem: State Mutation Loss & Query Contention
In traditional CRUD (Create, Read, Update, Delete) systems:
- Destructive Updates: When an account balance changes from 250 via an
UPDATE accounts SET balance = 250, the history of how and why that balance changed is permanently overwritten unless captured in complex audit logs. - Dual-Model Impedance Mismatch: The optimal data structure for high-throughput transactional writes (normalized 3NF relational tables or key-value rows) is completely unsuited for high-concurrency complex search, analytics, and mobile view queries (denormalized documents, Elasticsearch indexes, graph traversals).
- Resource Contention: Heavy analytical read queries compete with mission-critical OLTP write transactions for CPU, I/O and buffer cache (and, on lock-based engines or with locking reads, for locks too; MVCC engines' plain reads do not block writers).
Synthesizing vector architecture diagram...
Both panels handle the same deposit. In the "Traditional CRUD" panel, the write overwrites the balance in place (balance = 250), so the previous value is gone, and heavy reporting queries with joins run against the same tables and compete with writes for CPU, I/O and cache. In the "Event Sourcing + CQRS Architecture" panel, the command appends a DepositMade event to an append-only log and never updates anything; a projection engine reads the event stream and builds a separate read model (Elasticsearch, DynamoDB or Redis) shaped exactly for each query. You gain a full audit history and read and write sides that scale independently; the cost is that the read side is eventually consistent, since it lags the log slightly.
2. CQRS: Command Query Responsibility Segregation
Synthesizing vector architecture diagram...
3. Event Store Mechanics & Aggregate Snapshotting
1. The Append-Only Event Log
In Event Sourcing, the system of record is an append-only stream of immutable domain events:
json[ {"aggregate_id": "acc_101", "version": 1, "type": "AccountOpened", "data": {"owner": "Alice", "initial_balance": 0}}, {"aggregate_id": "acc_101", "version": 2, "type": "FundsDeposited", "data": {"amount": 500, "tx_ref": "tx_01"}}, {"aggregate_id": "acc_101", "version": 3, "type": "FundsWithdrawn", "data": {"amount": 200, "tx_ref": "tx_02"}}, {"aggregate_id": "acc_101", "version": 4, "type": "AddressUpdated", "data": {"city": "San Francisco"}} ]
2. Snapshotting Optimization for Long-Lived Aggregates
If an aggregate entity (e.g., a 10-year-old checking account) contains events, replaying all events on every write creates severe latency ( CPU time).
- Snapshot Rule: Every events, the system persists a point-in-time state snapshot in S3 or DynamoDB:
Hydrating the aggregate now requires loading only 1 snapshot delta events (never more than deltas, so the cost is bounded by , not by the aggregate's age).
Synthesizing vector architecture diagram...
Follow the chain left to right. Instead of replaying all 5,003 events since the account was opened, the system loads the latest snapshot, which records that the balance at version 5000 was $14,250. It then applies only the events after it: +$50 (5001), −$20 (5002) and +$100 (5003), giving the current state $14,380 at version 5003. Snapshots are only a cache: the events stay the source of truth, so a snapshot can be rebuilt or thrown away at any time. Take one every few hundred events, so loading an aggregate never replays a long history.
Unlock Complete Architecture & Production Runbooks
You have explored the free architectural preview (~52%). Spend 1 Coin to unlock the remaining 4 production deep-dive sections for a full 24 hours.