Explainstuff.mebeta
All concepts
Cloud Native Patternsintermediate7 min

Event Sourcing

Instead of storing where your data ended up, store every change that got it there — and rebuild the present from the past whenever you need it.

Picture a bank account row in a database. It says the balance is $40. That single number is true, but it's also strangely empty: was it always $40? Was it $1,000 yesterday? Did someone withdraw $960, or were there forty separate deposits of a dollar? The row can't tell you, because every update overwrote the one before it.

Event Sourcing is the idea that you should keep the story, not just the ending — record every change that ever happened, and treat that history as the real source of truth.

The problem

The default way we store data is update-in-place: there's a row for each thing, and when something changes we overwrite the relevant columns with the new values. A transactional store like this is simple and fast to query — the current state is sitting right there — but it quietly throws away everything that isn't current.

That loss bites in three ways. You have no real audit trail, because the old values are gone the instant they're replaced. You can't answer temporal questions like "what did this customer's cart look like last Tuesday?" — there's no last Tuesday to look at. And you lose the "why": you can see that the balance is $40, but not the deposit, the purchase, and the refund that produced it. Bolting on history columns and audit tables after the fact is fiddly, easy to get wrong, and never quite complete.

How it works

Event Sourcing flips the relationship between state and history. Instead of storing the latest state and discarding the changes, you store the full, ordered sequence of events that happened — MoneyDeposited, MoneyWithdrawn, RefundIssued — in an append-only log. Each event is immutable: once written it is never edited or deleted, only new events are added after it.

So where does the current balance come from? You replay the events. Starting from nothing, you apply each event in order — add the deposit, subtract the withdrawal, add the refund — and the value you're left with is the current state. The log is the source of truth; the current state is just a calculation over it. And because you can stop replaying at any point, you can reconstruct the exact state of the account at any past moment — a kind of time travel that update-in-place can never offer.

Append events, replay for state
event
Command
Event log
Read model
Each command appends an immutable event to the log; projections replay events to build read models.
Note

Commands and events are different things. A command is a request to change something — "withdraw $50" — and it can be rejected (insufficient funds). An event is a fact about something that already happened — "$50 was withdrawn" — and facts can't be argued with. The log only ever contains events, which is exactly why it's safe to treat as immutable history.

What you get for it

The payoff starts with a complete audit trail that you don't have to engineer — it's a side effect of how the system stores data. Every change is recorded, in order, with whatever context you chose to capture, and nothing is ever silently overwritten. For domains like finance, healthcare, or anything regulated, that's enormously valuable.

From the same log you get state reconstruction at any point in time, which makes debugging ("replay up to the moment it broke") and analytics ("what did things look like at quarter-end?") straightforward. And because each event is a clean, meaningful record of something that happened, those same events can feed other systems — you can publish them so other services react, or build entirely new views of the data later by replaying history through new logic.

It rarely travels alone

An append-only log is wonderful for writing and terrible for asking "what's the balance right now?" — you'd have to replay everything every time. This is why Event Sourcing is almost always paired with CQRS: the events on the write side are used to build separate, query-optimized read projections (a plain accounts table with a balance column, say) that the read side serves quickly. The log stays the source of truth; the projection is a derived, disposable view you can always rebuild.

The events are also a natural fit for pub/sub: as each event is appended, you publish it to subscribers, so other parts of the system react to changes without the writer knowing who's listening. Together these three patterns describe a common event-driven shape — write events, project them into read models, and broadcast them to whoever cares.

The trade-offs

None of this is free, and Event Sourcing is genuinely harder than an update-in-place table.

  • Querying current state takes work. You can't just SELECT the balance from the log; you need projections to read from, or snapshots (a periodically saved checkpoint of state) so replay doesn't have to start from the very first event every time.
  • The read side is eventually consistent. Projections are built from events after they're written, so for a brief window the read model lags behind reality — the same trade-off CQRS and pub/sub make.
  • Schema versioning is a real challenge. Old events are immutable and live forever, so when the shape of an event changes you can't go back and rewrite them. Your replay code has to understand every historical version of every event, or you need an upcasting strategy to translate old events into the new shape on the way in.
Watch out

The log only grows — design for that from day one. Events are never deleted, so an event store accumulates forever, and naive replay gets slower as history lengthens. Plan snapshots, retention and archival, and versioning before you go to production. Retrofitting them onto a log that's already millions of events deep is painful.

When to use it

Reach for Event Sourcing when history is part of the requirement, not an afterthought: when you need a trustworthy audit trail, when answering "how did we get here?" matters, or when you want to derive new views of past data you didn't anticipate. Domains with rich, meaningful state changes — finance, ordering, logistics, anything with a strong notion of what happened — tend to fit it well.

For a simple CRUD app where the current state is all anyone ever needs, it's overkill — a plain transactional update-in-place store is simpler, faster to query, and easier to reason about. Like most architectural patterns, Event Sourcing buys you audibility, temporal insight, and integration leverage in exchange for added complexity, eventual consistency, and the long-term burden of versioning your events.

Key takeaways

  • Event Sourcing stores the full, ordered sequence of immutable events that happened, not just the latest state — the log is the source of truth.
  • Current state is derived by replaying events from the beginning, so you can reconstruct the system as it was at any point in time.
  • It gives you a complete, tamper-evident audit trail for free, because nothing is ever overwritten or deleted.
  • It pairs naturally with CQRS (events build the read projections) and pub/sub (events are published to other systems as they happen).
  • The costs are real: querying current state needs projections or snapshots, the read side is eventually consistent, and evolving event schemas over time is genuinely hard.

Keep going