Event Sourcing
Instead of storing only the current state and overwriting it on every change, event sourcing stores the full sequence of facts that happened — the events — and derives current state by replaying them. The events are the source of truth; the state is a projection. It is the purest expression of our immutability preference, and it gives you a complete, tamper-evident history for free.
A traditional system keeps one mutable row per thing and updates it: a balance goes from 100 to 80, and the fact that it was once 100 is gone. An event-sourced system instead appends immutable events — FundsDeposited, PaymentMade — and computes the balance by folding over them. Nothing is ever updated or deleted; you only ever append.
Event sourcing is powerful but not free: it changes how you query, how you evolve schemas, and how you onboard the team. Reach for it where the history is itself valuable — money movements, AML/KYC decisions, anything an auditor or regulator will later interrogate — and not as a blanket default for every CRUD screen. It pairs naturally with CQRS (events feed read models), Event-Driven Architecture, and Audit Trails, and it is the deep end of Mutable vs Immutable Design.
Model facts, append only
- AlwaysMake events immutable, past-tense facts that already happened (`PaymentCaptured`, not `CapturePayment`). They are records of history, never commands or intentions.
- DoAppend events to the stream and never update or delete them. The event store is strictly append-only; that property is the whole point and must not be compromised for convenience.
- DoDerive current state by replaying (folding) a stream's events through a pure function. Keep that fold deterministic so the same events always produce the same state.
- DoVersion your event schemas and use upcasters to translate old events into the current shape on read. Old events are immutable, so you adapt at read time, never by rewriting history.
- ConsiderSnapshots for long streams: periodically store a computed state so you replay only events since the snapshot, not from the beginning. Snapshots are an optimisation, never the source of truth.
- NeverEditing or back-dating a stored event to "fix" data. Corrections are new compensating events appended after the fact, preserving what actually happened.
account.Balance -= amount; // 100 -> 80
db.Update(account);
// no record of why, when, or that it was ever 100
The current number survives; the facts that produced it do not. You can never answer "how did we get here?".
store.Append(streamId, new PaymentMade(amount, payee, now));
// current balance is derived, not stored:
var balance = store.Read(streamId).Aggregate(0m, (b, e) => e.Apply(b));
Every change is an immutable fact. State is computed from facts, so the full history — and the reason for every number — is always recoverable.
Use it where history is the value
- DoApply event sourcing to aggregates where the audit trail and temporal queries matter: ledgers, balances, screening lifecycles, case management. "What did it look like last Tuesday?" becomes a natural query.
- ConsiderA simpler append-only audit log or plain CRUD when you only need current state and don't need to rebuild or time-travel. Event sourcing is a serious commitment, not a free upgrade.
- DoSeparate the write model (event streams) from read models built by projecting events, so queries stay fast and don't replay streams on every request (see CQRS).
- AvoidEvent-sourcing every entity in the system by default. The operational and cognitive cost on low-value CRUD outweighs the benefit; choose deliberately per aggregate.
Operate it safely
- DoUse optimistic concurrency on append (expected stream version) so two concurrent writers cannot silently interleave conflicting events (see Concurrency).
- DoMake projections idempotent and able to be rebuilt from scratch by replaying the log, so a broken read model is always recoverable (see Data Integrity & Transactions).
- ConsiderPersonal-data and right-to-erasure implications up front: immutable events and GDPR Article 17 are in tension. Use crypto-shredding (delete the key) or keep PII out of the event body (see Data Protection & Privacy).
Self-review checklist
- AskIs the history of this aggregate genuinely valuable, or do I only ever need its current state?
- AskAre my events immutable past-tense facts, appended and never edited?
- AskCan I rebuild every read model purely by replaying the log?
- AskHow will I handle event schema changes and erasure of personal data without rewriting history?