Event Sourcing

Store what happened, not what is. Know how state is rebuilt from an append-only log, what snapshots and versions are for, and the costs that make this a pattern for parts of a system, not all of it.

Overview

Event Sourcing keeps every change to an object as an event in an append-only log and treats that log as the record. The current state is not stored as the truth; it is computed by replaying the events. A bank account is not a row with balance = 500. It is AccountOpened, Deposited 600, Withdrawn 100, and 500 is what you get when you add them up.

Martin Fowler's 2005 description is one line: capture all changes to an application state as a sequence of events. Because the events are kept, the state can be thrown away and rebuilt from an empty application, the state at any past moment can be recomputed, and a wrong event can be corrected by replaying the log with the fix in place. Greg Young's CQRS documents describe the same idea as events being the storage mechanism, with the aggregate rebuilt by applying its events in order.

Interviewers want three things: the write path (load the stream, decide, append), the cost of replay and how snapshots bound it, and the honest list of problems: eventual consistency of the views, schema changes on immutable data, and deleting personal data from a log that must not be edited.

State versus events

  • What is written — The new value, over the old one — A new event, after the old ones
  • What is read — The row — The stream, replayed (or a snapshot plus the tail)
  • History — Gone unless a separate audit log is kept — The record itself
  • Undo — Write the old value back, if you still have it — Append a compensating event; the original stays
  • Concurrent writers — Row locks, or last writer wins — An append is rejected if the stream moved since it was read
  • Query by any field — SQL — Only by stream; other questions need a projection

The last row is the one people forget. An event store answers 'what happened to account 42' well and 'which accounts are overdrawn' badly. Projections, tables built by consuming the events, answer the second kind of question, which is why Event Sourcing is almost always paired with CQRS.

The write path

Step 3 is what makes concurrent writers safe. The handler says which version it read; if another handler appended first, the store refuses the append, and the handler reloads and tries again. That is optimistic concurrency, and it replaces row locks.

A runnable account

The aggregate has two kinds of method. apply changes state from an event and has no rules; withdraw and deposit check rules and produce events. The store keeps one list per stream with a version, and rejects a stale append.

The last assert is Fowler's temporal query: replay up to a point and you have the state at that point.

Snapshots

Replaying a long stream on every load costs time. A snapshot is the aggregate's state serialized at some version; loading becomes 'read the latest snapshot, then replay only the events after it'. Young calls these rolling snapshots and stresses that they are a heuristic: the events stay the truth, and a snapshot can be rebuilt or thrown away at any time.

  • When to take one — Every N events, or by a background process watching the store — Bounds the worst-case load; the writer does not pay for it
  • Where to keep it — Beside the stream, keyed by version — Loading finds it in one read
  • What if it is wrong — Delete it and replay — Snapshots are derived; the stream is the source
  • Do views need them — Usually not — Projections are already the precomputed state

Changing events after the fact

Events are immutable, so a bug that wrote wrong events, or a business change that needs a new field, cannot be fixed by editing rows. The Azure Architecture Center lists the options; they go from harmless to last resort.

  • A wrong event happened — Append a compensating event that reverses it (ReservationCanceled after SeatsReserved) — Yes
  • A new optional field — Tolerant deserialization: readers ignore unknown fields and default missing ones — Yes
  • A changed meaning — A version in the event type or envelope; consumers pick handling by version — Yes
  • Many old versions — Upcasters: functions that convert old shapes to the current one when reading, chained as needed — Yes
  • Nothing else works — Rewrite historical events in place — No; breaks the audit trail

Personal data in an immutable log

A log that must not be edited meets laws that require deleting a person's data. Two designs handle it, both from the Azure guidance.

  • Keep personal data out of events — Events carry an identifier; the personal fields live in a separate, deletable store — Two reads to show a name
  • Crypto-shredding — Encrypt personal fields with a key per person; deleting the key makes the data unreadable while the event stays — Encryption on every read and write; key management

When it fits, and when it does not

The Azure page opens with a warning: this is a complex pattern that changes how you store data, handle concurrency, evolve schemas and query state, and it is costly to move to or away from. Fowler's page is a catalog of what it enables. Both are right, which is why the answer is 'for the parts that need it'.

  • A ledger or order pipeline where the history is the product — Profiles, settings, lookup tables
  • Rules that need the intent of a change (two seats reserved, not 'remaining changed to 42') — Forms that overwrite a record
  • Heavy write contention, where read-modify-write row locks become the bottleneck — Low write rates
  • Rebuilding views or adding new ones from history — Screens that must show a change the instant it is made
  • Teams that have run event-driven systems — Prototypes and short-lived systems

What you will be able to do

  • Define Event Sourcing in Fowler's one line and say what the record is.
  • Draw the write path and explain how an expected version replaces a row lock.
  • Write an aggregate with apply and decide methods and replay it to any point.
  • Explain snapshots as a bounded-cost optimization, never as the truth.
  • List the costs (views that lag, schema change on immutable data, personal data) with a strategy for each.

Interview questions

  • What is an event store, and how is it different from a table that holds the current state?
  • How do snapshots improve performance, and what can go wrong with them?
  • How do you handle a change to the shape of an event when old events cannot be edited?
  • What consistency and delivery problems does Event Sourcing introduce, and how do you handle them?
  • Design an event-sourced bank account. What are the events, the rules and the tests?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio