Event Sourcing: Rebuilding State From a Log of Truth


Most applications store state the same way: a row in a database represents the current truth, and every update overwrites what was there before. Event sourcing flips that model. Instead of persisting current state, you persist the sequence of events that led to it, and you derive state by replaying that sequence. The database row showing “balance: $42” is replaced by an append-only log of “deposited $50,” “withdrew $8,” and so on. The balance becomes a computed view, not a stored fact.

This isn’t a new idea. It borrows heavily from accounting ledgers, version control systems, and database write-ahead logs, all of which have used append-only history as the source of truth for decades. What’s changed is that more application teams are applying the pattern above the database layer, to domain models like orders, accounts, and workflows, rather than leaving it buried in infrastructure internals.

Why Give Up Current-State Storage

The appeal is that an event log never loses information. A traditional CRUD table tells you what something is right now, but not how it got there. If a customer’s shipping address is wrong, you can see the current value, but not who changed it or why. An event log preserves every state transition, which makes the system naturally auditable. That matters a lot in domains like finance, healthcare, and compliance-heavy industries where “how did we get here” is a real question someone will eventually ask, sometimes years later.

It also decouples the write model from the read model. Once you have a log of events, you can build multiple projections from it: a table optimized for lookups by customer ID, a search index, a dashboard aggregation, each rebuilt independently by replaying events through different logic. This pairing of event sourcing with CQRS (command query responsibility segregation) is common enough that the two are often discussed together, though they’re separable concepts.

The Cost of Replayable History

None of this comes free. The most immediate cost is complexity. Instead of reasoning about a row’s current value, developers have to reason about a fold over a sequence of events, and bugs in that fold logic can silently produce wrong state that looks fine until someone checks the math. Testing shifts from asserting on stored values to asserting on the outcome of replaying specific event sequences.

Performance is the second cost. Replaying thousands of events every time you need current state doesn’t scale, so most implementations introduce snapshotting: periodically materializing state and storing it alongside a pointer to “replay from here forward.” That reintroduces some of the caching and staleness problems event sourcing was meant to avoid, just at a different layer.

Schema evolution is the quieter, harder problem. A CRUD table’s schema can be migrated with an ALTER statement. An event log’s schema is effectively frozen the moment events are written, because old events still need to be replayable years later. Teams handle this with event versioning, upcasting old event shapes into new ones during replay, but it requires discipline that’s easy to skip early on and painful to retrofit later.

Where It Actually Fits

Event sourcing tends to earn its complexity in domains where the history itself has business value, not just the current snapshot. Financial ledgers, order fulfillment pipelines, and workflow engines are common fits because “what happened and in what order” is part of the product requirement, not just a debugging aid. It’s a poor fit for simple CRUD apps where current state is genuinely all anyone cares about, since the overhead of replay logic and snapshotting buys nothing there.

The broader lesson generalizes past any one architecture pattern: whether you should store facts or derived state depends on whether your domain needs history as a first-class citizen. Event sourcing is one of the more disciplined ways to make that call explicit, at the cost of asking every future engineer on the team to think in event streams instead of rows.