Skip to main content
CQRS & Event Sourcinglesson 2 of 4 · 2 min read

The Event Log as the Source of Truth

Store what happened, not where things stand

A normal database stores where things stand. The account row says the balance is 300, and yesterday's balance is gone unless you built history yourself.

Event sourcing turns that around. Your store holds an append-only sequence of things that happened, account opened, 500 deposited, 200 withdrawn, and the balance is simply what you get by replaying them in order.

Treat your events as immutable facts in the past tense. Nothing updates and nothing is deleted. A correction is a new event that reverses the old one, exactly as an accountant reverses an entry rather than erasing the line.

Borrow the accountant comparison, because they have run this model on paper for centuries, and your version control runs it for code, where commits are the events and a checkout is a replay.

What the log buys

Count what the log buys you. An audit trail nobody can quietly edit, which in regulated domains is a requirement and not a preference.

Time travel for debugging, so you can replay one customer's events up to Tuesday afternoon and see exactly the state that produced the bug.

And the freedom to answer questions you never planned for, because the raw facts are all still there. A system that overwrote its data can never reconstruct what a view would have said last quarter.

Write by loading that entity's events, folding them into current state in memory, checking your command against the rules, and appending new events if it passes.

Snapshot when the histories get long, because folding thousands of events per write gets expensive. Save the computed state every few hundred events and replay only the tail. A version check on the sequence number catches two writers appending at once, and the loser reloads and retries.

the shape of it
OpenedDeposited 500Withdrew 200Replay in orderBalance 300current state
step 1 of 2
The log is the truth; the balance is what you get by replaying it.
storing what happened instead of where things stand
Java
// Nothing updates and nothing is deleted. A correction is a new
// event, the way an accountant reverses an entry.
List<Event> history = store.load("account:42");

// Current state is a fold over the events, not a row you read.
Account acc = history.stream().reduce(new Account(), Account::apply);

// A write checks the rules against that folded state, then appends.
if (acc.balance() < amount) throw new InsufficientFunds();
store.append("account:42",
    new MoneyWithdrawn(amount, now()),
    history.size());          // version check: two writers cannot both win

// Long histories make the fold expensive, so snapshot every few
// hundred events and replay only the tail.

Worked example

Ana investigates a support ticket at a neobank: a customer insists his balance dropped 40 euros overnight with no transaction shown. In the bank's previous CRUD system this was a dead end, one overwritten row and shrugs. The new core is event sourced, so Ana pulls the account's event stream: 3,112 events since opening. Replaying to Friday 23:59 shows 412.80; Saturday's events include a CardAuthorizationSettled for 40 euros where the earlier authorization hold had displayed as pending, off-screen in the app's default view. Total investigation time: 25 minutes, and the answer comes with proof, the exact settlement event with its timestamp and merchant ID. The product team also gets a fix out of it: a projection change that shows settled holds inline, rebuilt from the same events over a weekend.