SaaS technology
Businesses SaaS August 12, 2026 • 8 min read

Event Sourcing Is Not an Audit Log (And Mixing Them Breaks Both)

For: A CTO at a 40-person B2B SaaS company who is being asked by a compliance-conscious enterprise prospect to prove point-in-time state reconstruction and is now evaluating whether to retrofit event sourcing onto their Postgres-backed monolith — having only ever read the enthusiast-written explainers that describe it as 'just storing events instead of state'

Event sourcing is a write-model consistency pattern. An audit log is a read-model compliance artifact. They both store a history of changes, which is why smart engineers keep conflating them — and why the conflation quietly wrecks both. If a compliance-conscious enterprise prospect is asking you to prove point-in-time state reconstruction, the right answer is almost never “let’s rewrite our Postgres write path as an append-only event log.” The right answer is usually a properly designed audit table (or CDC stream) alongside the boring, mutable state you already have.

This post explains why, using the vocabulary a CTO needs to push back on a bad architectural instinct before it becomes a six-quarter migration.

The problem event sourcing actually solves

Event sourcing exists because certain domains have a write model that is genuinely a sequence of decisions, not a snapshot. Trading systems. Banking ledgers. Multi-step order fulfillment. In these domains, the current state is a lossy summary of what happened, and the business needs the sequence itself to reason about invariants: “did we double-charge this account?”, “what was the order of fills?”, “can we replay this workflow with a bug fix applied?”

So instead of UPDATE accounts SET balance = 450 WHERE id = 7, you append MoneyWithdrawn{account: 7, amount: 50} to a log. Current balance is derived by folding events. The log is the source of truth. State is a cache.

That’s the pattern. Notice what it’s optimizing for: write-side correctness in a domain where the sequence of decisions is the business. Queryability, reporting, and compliance are downstream concerns you handle by building projections — read models generated by replaying events into whatever shape a consumer needs.

The problem an audit log solves

An audit log solves a completely different problem: proving to a third party (regulator, auditor, enterprise security team) that you can answer questions like “who changed this customer record on March 14, and what did it look like before?” The source of truth is still the current state. The audit log is a defensible, tamper-evident trail beside it.

What auditors actually want:

None of this requires event sourcing. A well-designed audit table populated by triggers, or a CDC pipeline off the Postgres WAL into an append-only store, delivers all of it without touching your write model.

The analogy: recipes vs. security cameras

Event sourcing is a recipe log in a kitchen. Every step is recorded because if you want to know why the sauce tastes wrong, you need the sequence of what the chef did. You can rebuild the dish by re-running the recipe. That’s valuable for the cook.

An audit log is a security camera. It doesn’t care about intent or workflow. It just records, from the outside, what changed and when, so an inspector can review it later. You wouldn’t cook from camera footage. You wouldn’t hand a recipe log to a health inspector.

Both record history. They’re not substitutes.

A minimal worked example

Say you’re a B2B SaaS with a subscriptions table. An enterprise prospect wants to know: “prove you can show me any customer’s subscription state on any given day for the last 3 years.”

The audit-log answer (30 lines of SQL, one week of work):

CREATE TABLE subscriptions_audit (
  audit_id      bigserial PRIMARY KEY,
  subscription_id uuid NOT NULL,
  changed_at    timestamptz NOT NULL DEFAULT now(),
  changed_by    text NOT NULL,
  operation     text NOT NULL, -- INSERT/UPDATE/DELETE
  before_row    jsonb,
  after_row     jsonb,
  request_id    text
);
-- trigger populates this on every write to subscriptions

Point-in-time query: find the latest after_row for a given subscription_id where changed_at <= '2024-03-14'. Done. Auditors get SQL. Your write path is untouched. Retention lives in one table you can partition, archive, and hash-chain independently.

The event-sourcing answer: redesign subscriptions as a stream of SubscriptionCreated, PlanChanged, PaymentFailed, Cancelled events. Build a projection that folds them into current state for your app. Build another projection that materializes point-in-time views for auditors. Handle event schema versioning forever. Handle upcasters when a field’s meaning changes. Handle the fact that GDPR “right to be forgotten” is genuinely hard in an append-only log. Rewrite every command handler in your monolith.

Both approaches produce point-in-time reconstruction. Only one of them requires you to rethink your entire write model to satisfy a read-side requirement.

Event sourcing gotchas the enthusiast blogs skip

None of these are dealbreakers if your domain needs event sourcing. All of them are unforced errors if you adopted it to satisfy a compliance question.

When to use event sourcing (and when not to)

Use it when:

Do not use it when:

For the CTO scenario at the top of this post: build the audit log. Postgres triggers into a partitioned audit table, or Debezium into an append-only store if you want tamper evidence and separation from the OLTP database. Ship it in weeks. Close the deal. Revisit event sourcing only if you find an aggregate in your domain where the sequence of decisions is genuinely the business — and then apply it to that aggregate, not the whole system.

If you’re weighing this kind of architectural decision as part of a broader legacy modernization effort, the same principle applies to most “should we rewrite it in X” questions: figure out which requirement is actually driving the ask, and solve that.

Frequently Asked Questions

Can I use event sourcing as my audit log?

Technically yes, practically no. Your event store speaks the domain’s language (PlanUpgraded, InvoiceSettled) — auditors want row-level before/after diffs with user attribution. You’ll end up building an audit projection on top of your events, at which point you’ve built both patterns and coupled them. Just build the audit log directly.

What’s the difference between event sourcing and change data capture (CDC)?

CDC (e.g., Debezium reading the Postgres WAL) streams row-level changes out of your database into another system. Your write model stays as normal CRUD. Event sourcing replaces your write model with an append-only log of domain events. CDC is a plumbing pattern; event sourcing is a modeling pattern. For audit and compliance, CDC is almost always the right tool.

How do I prove point-in-time state reconstruction without event sourcing?

An audit table with before_row and after_row JSONB columns, populated by triggers or CDC, lets you reconstruct any row as of any timestamp with a single SQL query. Combine with hash chaining or WORM storage if the auditor requires tamper evidence. This satisfies SOC 2, HIPAA, and most enterprise procurement checklists.

Is event sourcing worth it for a B2B SaaS monolith?

Usually not for the whole system. It can be worth it for specific aggregates where the sequence of events is the business — billing ledgers, subscription lifecycle, approval workflows. Applying it globally to a CRUD-heavy product buys complexity you’ll pay for on every future feature.

We’ve already started retrofitting event sourcing. Should we stop?

Depends how far in you are and whether the original driver was a real domain need or a misread compliance requirement. If it was the latter, cut losses on the untouched aggregates and ship an audit log for the compliance ask. For a second opinion on an in-flight architectural pivot, contact CodeNicely for a personalized assessment.

Found this useful? CodeNicely publishes engineering and product playbooks weekly. Browse the archive or tell us what you're building.