advanced 3 min answer

A neobank stores every account change as an immutable fact rather than a mutable balance. Which needs justify this, how are balances materialised for fast reads, and what happens when a projection is found to be wrong?

nubankevent-sourcingledgerimmutabilityaudit
Show the full answer Hide the answer

What justifies it

Three needs, and the first two are usually the deciding ones in a regulated financial context:

  • Auditability that is structural rather than added. A regulator asks how a balance came to be what it is on a given date. With immutable facts, the answer is a query rather than an archaeology project, because the history was never overwritten.
  • Reconstruction at any point in time. Disputes, chargebacks and investigations are all "what was true on date D", which a mutable-balance model cannot answer without a separately-maintained audit log that is itself a second source of truth capable of disagreeing with the first.
  • Debugging. When a balance is wrong, the sequence of facts that produced it is available, and the bug is visible rather than inferred.

The domain fits naturally: a ledger is already an append-only structure. Double-entry bookkeeping has been immutable-fact-based for centuries, and a mutable balance column is the unnatural representation.

Materialising balances

The balance is a fold over the facts, and computing it from the beginning of history on every read is untenable. The standard approach:

  • Snapshots: a periodic materialised balance with a marker for the last fact included. A read loads the snapshot and applies only the facts since. Snapshot cadence is a tuning knob between read cost and storage.
  • A continuously-maintained projection updated as facts are appended, serving reads directly.
  • Crucially, the projection is a cache, not the truth. It can always be discarded and rebuilt. That property is what makes the whole design safe, and it must be true operationally, not just in principle — there must be a tested, timed rebuild path.

When a projection is found to be wrong

This is the question that separates people who have run event-sourced systems from people who have read about them, and the answer is the design's principal advantage:

  1. The facts are not wrong. Only the derived view is. Nothing needs to be corrected in the source of truth.
  2. Fix the projection code.
  3. Rebuild the projection from the facts — into a new projection alongside the existing one.
  4. Compare old and new, quantify the divergence, and identify which customers were affected and by how much, which is a query rather than an estimate.
  5. Swap the read path to the new projection.

Compare with a mutable-balance system: the wrong value is the truth, the correct value is unknown, and recovery means restoring a backup and replaying whatever can be found. The difference in blast radius is the entire argument for the pattern.

The costs, stated honestly

Schema evolution of facts is permanent — a fact written five years ago must still be readable, so every version of every event type lives forever in the deserialisation path. Storage grows monotonically, needing a tiering strategy. Eventual consistency between the fact log and projections must be surfaced in the UI. Erasure requirements collide with immutability, which is the genuinely hard one: the usual resolution is crypto-shredding — personal data encrypted per subject, with erasure implemented by destroying the key, leaving the facts structurally intact but unreadable.

And the discipline required is real: the moment someone "fixes" a balance by writing a corrective fact without a corresponding real-world event, the log stops being a record of what happened and the property that justified the whole design is gone.