Stop storing the current state. Store the events that produced the state, and compute the rest. The architectural pattern behind every banking ledger, every Git repo, every audit trail worth its name. A small mental shift that quietly changes everything else.
Your bank has a ledger. The balance is what they compute from the ledger. Every deposit, every withdrawal, every fee, every interest accrual is written down, in order, never erased. Ask "what's my balance?" and they don't read a number from a column — they replay the history and tell you the sum. Every regulated financial system on earth works this way. Banks have known for centuries that storing the current state isn't enough. The events that produced the state matter more than the state itself.
And yet, the default architecture for almost every business system is the opposite. CRUD: Create, Read, Update, Delete. A row in a database represents the current state. Each update overwrites the previous value. The history of how you got here is gone — unless you specifically engineered it back in, usually with audit tables, change-data-capture, or "snapshots." This default is so universal we forget it's a choice. Event sourcing is a different choice.
The pattern was formalized as "event sourcing" by Greg Young and Martin Fowler in the mid-2000s, building on patterns already common in banking, accounting, and version control. Git is the most-used event-sourced system on the planet — every commit is an event; the working tree is just one projection. So is your accounting software. So is the operations log in any database. What event sourcing does is take that pattern, which was always there for high-stakes systems, and make it a deliberate architecture choice for ordinary ones.
The diagrams look almost identical. The code looks similar. The mental model is entirely different. In CRUD, current state is the truth, history is a side effect. In event sourcing, history is the truth, current state is a side effect — derived by replaying events. This sounds like a small flip; it isn't. It changes what you can ask, what you can debug, what you can build on top of the system after the fact.
A single accounts row. Every deposit, withdrawal, fee changes balance in place. Reading is fast (one query, one row, one number). Writing is fast (one UPDATE statement). History is a separate concern — usually solved with audit-trigger tables, change-data-capture, or "just check the logs."
An append-only events log. Every deposit, withdrawal, fee is a new row — nothing is ever updated or deleted. Reading current state requires replaying events (cached as a "snapshot" for perf). History is the data; current state is a view over it.
Notice what's not in the events table: a balance column. There's no current state stored anywhere. The balance only exists as a function over events: balance = sum(deposits) - sum(withdrawals) - sum(fees) + sum(interest). Run that function over the full event log and you have today's balance. Run it over events up to last Tuesday and you have last Tuesday's balance. Time-travel queries that would require complex audit tables in CRUD become free in event-sourced systems.
The trade is real and bidirectional. Writes get an audit trail for free; reads get more expensive — instead of one indexed lookup, you need to either replay events or cache a derived "projection." For low-write/high-read workloads (a dashboard), the projection cache hides the cost. For high-write/high-read workloads (a real-time game state), the cost is harder to swallow. This is why event sourcing isn't the default for everything — but it earns its complexity in the workloads that need its strengths.
The conceptual machinery of event sourcing is small. Five terms cover almost everything you'll read in the literature — Greg Young's talks, Vaughn Vernon's books, the EventStoreDB docs, Marten's docs, Axon Framework's reference. Internalize these five and the rest of the topic unpacks naturally.
A request to change state. Phrased in the imperative: DepositMoney, RegisterUser, ShipOrder. Commands can be rejected (insufficient funds, user already exists) — they aren't yet facts. They're proposals.
The thing the command operates on (an Account, an Order, a User). It loads its own past events, decides whether the command is valid given that history, and emits one or more new events. Aggregates enforce invariants — "you can't withdraw more than your balance" lives here.
An immutable fact about what happened. Phrased in the past tense: MoneyDeposited, UserRegistered, OrderShipped. Events can't be rejected — they already happened. They're the truth of the system. The persistence model is "append, never update, never delete."
The append-only log holding every event the system has ever emitted, grouped per aggregate. EventStoreDB, Marten (on Postgres), Axon Server are purpose-built; many teams use Postgres or Kafka as event stores with conventions. The only operations allowed are append and read.
A derived read-model built by consuming events. Each projection answers one question: "what's the current balance?", "what's the transaction history?", "what's monthly spending?". Projections can be rebuilt from scratch by replaying the log. Add a new feature? Add a new projection; backfill from history.
The flow: a command hits an aggregate, which checks invariants against its loaded event history and emits new events to the event store. Downstream, multiple projections consume those events to build the read-models the rest of the system queries. Commands write; projections read. The event store is the single source of truth in between. This shape — separating write from read with an immutable log between them — is exactly what CQRS (the next module) generalizes.
Below: a bank account modeled as an event-sourced aggregate. Click the action buttons to append events to the log. Watch all three projections — Balance, Transaction History, Monthly Stats — update live from the same source. Then drag the time-travel slider backward to see the account's state at any past point. The events don't move; only the cutoff does.
Event sourcing is a powerful tool. It's also a complexity tax — pay it on the wrong workload and you've made the system harder for no reward. Greg Young, who named the pattern, has been the loudest voice arguing it's over-applied: most CRUD systems would suffer if migrated, not benefit. The right question isn't "is event sourcing good?" but "does this particular domain reward the trade-offs?"
UserRegistered_v2), upcasters that transform old events on read. Every long-lived event-sourced system has scar tissue around this.The honest summary: event sourcing fits well when history matters as much as state — finance, healthcare, supply chain, audit-heavy systems, anything where regulators or accountants will eventually ask "what happened in October?" It fits poorly for ephemeral state (cache layers, real-time chat presence), high-write low-history domains (sensor telemetry where you just want the latest reading), and small teams without the bandwidth to absorb the learning curve.
A common middle path: event sourcing for the core domain, CRUD for the periphery. The accounting / order / inventory parts of an e-commerce system are event-sourced; the user-profile / preferences / search-index parts are CRUD. You don't have to pick one for the whole system. The next module — CQRS — formalizes this by separating write models from read models even more deliberately, and shows how event sourcing and CQRS pair naturally.
The terms every event-sourcing tutorial, every EventStoreDB doc, every DDD book assumes you already know.
DepositMoney, RegisterUser). Commands can be rejected by aggregates if they violate invariants. Commands aren't yet facts.MoneyDeposited, OrderShipped). Events can't be rejected — they already occurred. They are appended forever.Test the event-sourcing intuition. Click an answer; explanation drops in instantly.
Perfect. Commands, events, aggregates, projections — the whole pattern. Now: separating the read side from the write side, deliberately.
The mental shift that changes how you reason about state, history, and recovery.
Events are the truth; state is a function over events. Internalize this and time-travel, audit, and replay all become trivial.
Command, aggregate, event, event store, projection. The whole vocabulary of every event-sourcing system fits in those five terms.
Schema evolution, projection lag, learning curve. Event sourcing earns its complexity where history matters; it's baggage where it doesn't.