Event sourcing gets the attention, but the operational payoff is replay: taking a stored log of events and re-running it through the fold logic to produce a state. In an ERP context that mechanism turns into three very concrete capabilities — reconstructing what a ledger looked like at any point in time, rebuilding a corrupted or outdated read model without a special-case migration script, and recovering from an outage by re-deriving state instead of restoring from a backup that's hours stale. This is about the mechanics of replay itself and where it earns its keep in a transactional system.
What replay actually does
Replay is unglamorous: read a stream (or the whole log) from a starting point, in order, and feed each event through the same fold function that built the state the first time. Nothing about the event handling code needs to know it's being replayed versus processed live — that's the property you're relying on. The moment your event handlers do something replay-unsafe (call an external payment gateway, send a customer email, increment a counter that isn't derived purely from the event's own data) you've broken the ability to safely replay, because replaying now re-triggers side effects that already happened.
State-changing folds (what changes the balance, what changes the status) must be pure functions of the event. Side effects (send an email, call a webhook, post to a bank API) belong in a separate handler that's easy to disable during a replay — usually gated behind a flag like isReplay or by only wiring those handlers to the live subscription, not the historical one.
Rebuilding read models and projections
The most routine use of replay is projection rebuilds. A denormalized "open AR by customer" report table is a projection — it's derived, not authoritative. When you ship a bug in the projection logic, add a new column, or the table gets corrupted by a bad deploy, the fix is the same: truncate the projection, reset its checkpoint to the start of the log, and let it consume the event stream again from zero. No backfill script, no special one-off migration — the same consumer code that built it the first time builds it correctly the second time, because it's just a fold over the same immutable events.
-- Reset the projection's checkpoint and clear the stale read model
BEGIN TRANSACTION;
TRUNCATE TABLE ar_open_balance_by_customer;
UPDATE projection_checkpoints
SET last_processed_position = 0,
status = 'rebuilding'
WHERE projection_name = 'ar_open_balance_by_customer';
COMMIT;
-- The projection worker picks this up, streams the event log from
-- position 0, and re-applies every ARInvoiceIssued / PaymentApplied /
-- InvoiceVoided event through the same fold that built it originally.
Point-in-time reconstruction and audit
Replaying only up to a given event, rather than to the end of the log, gives you the state of an account, order, or ledger as of a specific moment. That's the mechanism behind "what did this customer's balance look like at month-end close" or "show me the exact sequence of adjustments that got this invoice from $4,200 to $3,850" — you don't need a separate audit table maintained by hand, because the event log already is the audit trail. This is the single biggest reason finance and audit teams push for event sourcing in ERP: the question "how did we get here" has a deterministic, replayable answer instead of a best-effort reconstruction from timestamps and change logs.
Disaster recovery: replay instead of restore
A conventional DR strategy restores the last snapshot/backup and accepts the data lost between the backup and the incident (your RPO). If your event log is durably persisted and replicated independently of the derived state stores, DR looks different: restore the event log (which is smaller, append-only, and easier to replicate cheaply), then replay it into fresh read models and aggregate snapshots. Your recovery point becomes "the last durably committed event" rather than "the last nightly backup," which is a materially better RPO for systems where losing a day of transactions is not acceptable.
Know how long a full replay takes before you need it in an incident. If replaying a year of events takes six hours, that's your effective RTO for a from-scratch rebuild — budget snapshots (periodic saved folds) so a targeted replay only has to cover events since the last snapshot, not the entire history.
Debugging: replay as a diagnostic tool
Because replay is deterministic and side-effect free (once you've isolated the pure fold from the side effects, per the callout above), it doubles as a debugging tool. Take a copy of the real event stream for the affected aggregate, run it through the fold in a local environment with a debugger or extra logging attached, and step through exactly the sequence that produced the bad state — no need to reproduce the bug live in production. This is a meaningfully better debugging experience than working backward from a corrupted row and a change log, because you have the literal, ordered inputs that produced the output.
| Use case | What gets replayed | Payoff |
|---|---|---|
| Projection rebuild | Full stream, into a fresh read model | No hand-written backfill scripts |
| Point-in-time audit | Stream up to a target event/timestamp | Deterministic "how did we get here" |
| Disaster recovery | Log since the last durable snapshot | RPO measured in events, not backup windows |
| Production debugging | One aggregate's stream, offline | Reproduce the exact failing sequence |
Replay is what makes the rest of event sourcing pay for itself in an ERP setting — the log isn't just a compliance artifact sitting unused, it's the mechanism you reach for on a bad Tuesday when a projection is wrong, an auditor wants a reconstruction, or a datacenter had a bad night. The discipline it demands in return is keeping every fold pure and every side effect out of the replay path, because the day you need to replay is the worst possible day to discover an email handler fires twice.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.