Event Sourcing
- Pradeep P
- 3 days ago
- 3 min read
Series: Modern System Design · Layer 4 — Data
Layer 4 · Post 10 of 11
← Previous: CQRS → Next: Change Data Capture
Layer 4 — Data · Post 49 of 88
Event sourcing stores every change as an event, then rebuilds state by replaying them. The log becomes the source of truth.
What you'll learn
Why the event log is the system of record, and snapshots are just a cache of the fold
How this pairs with CQRS without being the same thing
Versioning, GDPR, and "we can always rebuild" as operational traps
The idea in one minute
Most databases store current state: orders.status = 'shipped'. Event sourcing stores what happened: OrderPlaced, PaymentCaptured, OrderShipped. Current state is a function of the log — replay events in order.
e1 OrderPlaced e2 PaymentCaptured e3 OrderShipped | v fold --> { status: shipped, paid: true } // derived
The write model is an append-only stream per aggregate (order-123, account-9). A command loads (or snapshots) the stream, checks invariants, appends new events. Readers usually query a CQRS projection, not the raw log.
Why it matters
Sagas and audits want history: not just that the balance is 40, but that it was 90, then a refund, then a chargeback. Ledgers, bookings, and workflow engines already think this way.
Interviewers distinguish this from "we publish Kafka events." Publishing events is notification. Event sourcing means if you lose the events, you lose the business. Kafka can be the store; a Postgres events table can too. The rule is: the log is authoritative, the table of "current orders" is a projection.
How it works
Command comes in (ShipOrder).
Load aggregate: replay events, or load a snapshot plus events after it.
Decide: if already shipped, ignore or error (idempotency).
Append OrderShipped with a stream version (optimistic locking on the log).
Projectors update read models (CQRS). They subscribe to the log or to CDC of the events table.
Optimistic locking is natural: INSERT ... WHERE stream_version = $n. Two concurrent commands, one wins, one retries — post 43 on a stream.
Snapshots are optional performance, not truth. Wrong snapshot code poisons every replay until you fix it and rebuild.
A simple example
A bank account stream: Opened, Deposited(100), Withdrawn(30). Balance 70 is folded. You never UPDATE accounts SET balance = 70 as the source of truth. A fraud query can replay "all withdrawals last night." A CQRS view serves the mobile app balance with a few milliseconds of lag.
Stripe's objects plus an events API are spiritually close: you reason in events even if their public API also shows current state.
Common mistakes
Event sourcing the whole company. User profiles and CMS pages want current state. Use it where the history is the product.
Mutable events. "We will just fix event 400." Then every projection must know about the lie. Prefer new compensating events.
GDPR as an afterthought. "Right to be forgotten" vs append-only logs: encrypt PII, tombstone, or split PII out of the stream.
Replaying forever on every request. Snapshots and read models exist. The log is for writes and rebuilds.
Mixing transport and store. A Kafka topic with 7-day retention is not a ledger.
How this shows up in real systems
EventStoreDB, Akka Persistence, Axon, Marten (Postgres): purpose-built or library ES.
Kafka as event store: possible with compacted topics / infinite retention and discipline.
Accounting ledgers, game item histories, Temporal workflow history: event-sourced in spirit.
Git: snapshots + log; useful analogy, not an architecture copy-paste.
Recap
Event sourcing: append facts, derive state. Snapshots and CQRS views are caches.
Use it when audit and rebuild matter; do not use it as a default ORM.
Feeding other systems from a state database without dual writes is CDC — last post in this layer.
Series: Modern System Design · Layer 4 — Data
Layer 4 · Post 10 of 11
← Previous: CQRS → Next: Change Data Capture



Comments