ERP systems were financial-integrity-first long before "event sourcing" was a name anyone used — double-entry bookkeeping is, structurally, an append-only ledger of immutable, balanced entries, which is close to what an event log is. That overlap is why event sourcing keeps coming up in ERP architecture conversations. It's also why applying it is harder than the general pattern suggests: ERPs are built around relational, batch-oriented assumptions (period close, trial balance, immediate consistency within a document) that don't automatically cooperate with an eventually-consistent, event-first model.
Why the fit looks natural
A general ledger already behaves like an event log: you don't edit a posted journal entry, you post a reversing entry. An invoice, once issued, isn't edited in place when it's later adjusted — a credit memo or an adjustment references it. Accountants have been doing "append, never mutate, and derive the current balance by summing history" for centuries; event sourcing just formalizes that as a software architecture pattern and extends it past the general ledger into every module — AR, AP, inventory, order management — that touches money or quantity. The audit trail requirement that every finance system has to satisfy (who changed what, when, and what did it look like before) is close to free when the events themselves are the audit trail, instead of being a bolt-on change-log table that has to be kept in sync by hand.
The double-entry invariant is the hard part
Here's where it gets genuinely difficult. Double-entry accounting has a non-negotiable invariant: every transaction's debits must equal its credits, and the trial balance must always reconcile to zero. In a naive event-sourced design, a single business action — "ship this order" — might emit several independent events (InventoryDecremented, CostOfGoodsSoldPosted, RevenueRecognized) that each get folded independently by their own projections. If those projections can process events out of order, or partially, or one succeeds while a related one is still catching up, you can observe a moment where the books don't balance — which is not an acceptable state for a ledger to be caught in, even momentarily, for an auditor or a real-time balance check.
The fix is to make the atomic unit of the event itself carry the full balanced journal entry — a single JournalEntryPosted event containing all of its debit and credit lines — rather than splitting a transaction into multiple independently-replayable events that only balance once every one of them has landed. The append to the log must be all-or-nothing at the granularity accounting requires it to be atomic.
Eventual consistency vs. "the balance must be right now"
Projections in an event-sourced system are, by construction, eventually consistent — there's a lag, however small, between an event being appended and every downstream read model reflecting it. That's fine for a dashboard. It's a real problem for a credit-limit check at the point of sale: if the "customer AR balance" read model is a few hundred milliseconds behind the actual event log, you can approve an order that pushes a customer over their credit limit because the check read stale state. ERP workflows have operations that need a strongly-consistent answer, not an eventually-consistent one, and pretending otherwise produces defects that only show up under load.
-- One event, one atomic append, debits == credits enforced
-- before the event is allowed onto the stream.
{
"eventType": "JournalEntryPosted",
"streamId": "gl-2026-07",
"entryId": "JE-000481",
"postedAt": "2026-07-22T14:03:11Z",
"lines": [
{ "account": "1200-AR", "debit": 4200.00, "credit": 0 },
{ "account": "4000-REV", "debit": 0, "credit": 4000.00 },
{ "account": "2200-TAX", "debit": 0, "credit": 200.00 }
]
}
-- Invariant checked at append time: SUM(debit) == SUM(credit)
-- A failing check rejects the append; it never reaches the log.
The practical answer most teams land on is a hybrid: the strongly-consistent, must-never-be-wrong operations (does this posting balance, is this account within its credit limit right now) are enforced synchronously against a transactional store at write time — often the event append itself is guarded by the invariant check, as in the example above — while the broader reporting, analytics, and history-dependent views stay as eventually-consistent projections built by replay. Pure event sourcing, applied everywhere with no exceptions, tends to be the wrong answer for the paths that need an immediate, authoritative yes/no.
Batch-oriented processes don't disappear
ERP has real batch processes — period close, depreciation runs, revaluation, consolidation — that operate on the state of an entire ledger at a point in time, not on individual events as they arrive. Event sourcing doesn't replace these; it changes what they read from. A period-close routine becomes a process that replays (or reads a snapshot of) all events up to the close date, computes the close, and appends the resulting closing entries as new events themselves — closing the loop rather than writing outside it. Teams that skip this and let batch processes write directly to a separate mutable store end up with two sources of truth that drift apart, which defeats the entire point of adopting event sourcing in the first place.
What to actually source as events
Not every entity in an ERP benefits equally. Financial transactions, inventory movements, and anything with an audit or reconciliation requirement are strong candidates. Reference/master data — a vendor's address, a product's description — rarely needs event-sourced history; a simple audited table with an updated_at and a change log is the right-sized tool, and forcing it into the event model is unnecessary ceremony for data that has no meaningful "history of transactions" shape.
| ERP concern | Event sourcing's answer |
|---|---|
| Audit trail | The event log is the trail; no separate audit table to keep in sync |
| Double-entry balance | Journal entries must be atomic, balanced events — not split across several |
| Real-time credit checks | Enforce synchronously at write time; don't trust an eventually-consistent projection |
| Period close | A replay-driven process that appends its own closing events |
| Master data | Usually doesn't need event sourcing at all — a plain audited table is enough |
Event sourcing suits ERP better than most domains because accounting was already thinking this way, but the parts of ERP that require immediate, non-negotiable consistency — a balanced trial balance, a credit limit enforced in real time — are exactly the parts where naive eventual consistency breaks something an auditor or a customer will notice. The systems that get this right treat the balanced transaction as the atomic event, and reserve eventual consistency for the reporting and history use cases where a few hundred milliseconds of lag was never going to matter.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.