DEV Community

Cover image for The Audit Trail You Already Have Is the Bug Fix You've Been Missing
turboline-ai
turboline-ai

Posted on

The Audit Trail You Already Have Is the Bug Fix You've Been Missing

There is a persistent belief in the developer community that Event Sourcing makes bugs harder to fix. The assumption goes something like this: when your state lives in an append-only log instead of a mutable database row, you lose the ability to just go in and correct things. You can not run a quick UPDATE statement and move on with your day.

That belief gets the tradeoff exactly backwards.

The "Just Run a Migration" Myth

When a bug corrupts data in a traditional CRUD system, the usual instinct is to write a SQL migration. Find the bad rows, calculate what they should be, update them. Ship it. Done.

Except it is almost never that clean.

Before you write a single line of SQL, you have to answer a set of questions that are genuinely hard. Which records are affected? When did the bug get introduced? Did the bad data propagate to other tables through a join or a denormalized column? Did any downstream service consume the corrupted values and write something of its own? Was there a rate change or a business rule boundary that splits affected records into two different groups?

These are detective questions. And in a traditional system, you are solving them without a case file. You have the current state of the database and whatever you can piece together from application logs, if those logs were structured well enough to be useful, and if they were retained long enough to still exist.

The investigation still happens. It just happens in the dark.

What Event Sourcing Actually Gives You

In an event-sourced system, the investigation starts with real evidence. Every state transition that ever occurred is recorded as an immutable fact. You do not have to guess what the data looked like at a given point in time. You can look.

Take a concrete example: a tourist tax calculation bug that was live for three weeks before anyone noticed. During those three weeks, a rate boundary crossed. The tax percentage changed on day eleven. So there are two cohorts of affected records, each requiring a different correction.

In a traditional system, identifying those two cohorts means cross-referencing timestamps against some external source of truth for when the rate changed, then writing conditional logic into your migration, then hoping you got it right, then auditing the results manually.

In an event-sourced system, the events themselves carry the timestamps and the context. You can write a projection that reads the raw event stream, applies the corrected tax logic, and produces the right totals. No guessing. No patching state you can not fully reconstruct. The correction is a new event appended to the stream, and the projection replays cleanly from there.

Something like this:

def recalculate_booking_tax(events, corrected_tax_rate_fn):
    state = {}
    corrections = []

    for event in events:
        if event["type"] == "BookingConfirmed":
            booking_date = event["timestamp"]
            original_tax = event["data"]["tourist_tax"]
            correct_tax = corrected_tax_rate_fn(booking_date, event["data"]["base_amount"])

            if original_tax != correct_tax:
                corrections.append({
                    "type": "TouristTaxCorrected",
                    "booking_id": event["data"]["booking_id"],
                    "original_tax": original_tax,
                    "corrected_tax": correct_tax,
                    "reason": "BugFix-TaxRateBoundaryError",
                    "timestamp": now()
                })

    return corrections
Enter fullscreen mode Exit fullscreen mode

This is not pseudocode for illustration purposes only. This is roughly the shape of how correction logic actually works in practice. You write a function that reads history, compares it to what should have happened, and emits correction events. The log stays intact. The correction is traceable. You know exactly what changed and why.

The Shift in What Is Hard

The real difference between debugging in Event Sourcing versus traditional systems is not that one is harder. It is that the hard part moves.

In a mutable system, the hard part is reconstruction: figuring out what state existed, when, and for what reasons, using incomplete information.

In an event-sourced system, the hard part is modeling: figuring out what correction event accurately describes the fix, and making sure your projections apply it consistently.

Modeling is hard. But it is a productive kind of hard. It forces you to reason about your domain precisely. It leaves a record. And it is auditable in ways that a migration script run against production at 2am never will be.

Concrete Takeaway

If you are evaluating Event Sourcing and someone raises "but what about bug fixes" as a serious objection, the right response is to ask what their current process looks like for fixing corrupted data in a traditional system. It is rarely as simple as it sounds. Event Sourcing does not remove the complexity of data bugs. It just gives you better tools for dealing with them, starting with the audit trail that traditional systems never had in the first place.

Top comments (0)