Event Sourcing for a General Ledger: What It Actually Buys You


Event sourcing gets pitched as an architecture pattern — store the sequence of things that happened, derive current state by replaying them, keep a full history for free. All true, and also, for a general ledger specifically, beside the point that actually matters: it’s the mechanism that makes a compliance claim structurally true instead of merely asserted.

The Shape of the Model

A LedgerAccount aggregate doesn’t store a mutable balance field that gets incremented and decremented. It stores a sequence of events — AccountOpened, JournalEntryPosted, JournalEntryReversed — and the balance is a projection: a read model computed by folding those events, rebuildable from scratch at any time. The event store itself is append-only. There is no UPDATE or DELETE path for a posted event, by construction, not by convention.

Commands are the only way to produce new events, and a command handler validates before it emits anything — PostJournalEntry checks that debits equal credits before the event exists at all. An unbalanced entry never becomes a persisted fact; it’s rejected at the boundary.

Why This Is a Compliance Property, Not Just a Data-Modeling Choice

Three things fall out of this shape without any extra compliance-specific code:

  1. The audit trail is the primary data, not a side effect of it. There’s no separate “audit log” table that could fall out of sync with the real data — the event stream is the real data. Auditors don’t get a description of what happened; they get what happened.

  2. Corrections are visible, not silent. Fixing a mistake means posting a new JournalEntryReversed event, timestamped and attributed like every other event. There’s no code path that erases the fact an error occurred — the history of “we were wrong and then corrected it” is itself permanent.

  3. Retention is automatic, not policy-dependent. SOX 802’s document-retention concern assumes someone might delete or alter a record. In an append-only store, “someone deleted a record” isn’t a policy violation to catch after the fact — there’s no operation that does it.

What Event Sourcing Doesn’t Buy You

To be precise about scope: event sourcing governs the core ledger domain specifically — the part where a full history of what happened is the product itself, not an add-on. Auth and identity in this system are deliberately plain CRUD, not event-sourced, because a session either is or isn’t valid right now; there’s no compliance value in replaying login history the way there is in replaying financial history. Applying one pattern uniformly everywhere would be a bigger architecture than the problem calls for. See the linked ADRs on the developers page for exactly where that boundary sits.

← Back to the blog