What Is Event Sourcing? Stop Losing the Why Behind Your Data
For: A COO or product lead at a 50–200-person B2B SaaS company who keeps getting burned by support tickets they cannot answer — a customer's account is in a broken state, no one can reconstruct how it got there, and the engineering team says the database only stores what is true now, not what happened
Event sourcing is a way of storing data where you save every change as an immutable event, and derive the current state by replaying those events in order. Instead of a customer record that says balance: $412, you have a log that says deposited $500, withdrew $88, applied $0 fee — and $412 is just what you get when you add them up. This matters because when something breaks, you can replay history to see exactly how the account got into a bad state. Your database stops throwing away the why.
If you run a SaaS product and your support team keeps escalating tickets that engineering cannot answer — “the invoice total is wrong,” “the account is stuck in this weird status,” “the customer swears they never enabled this” — you are paying the price of a data model that only remembers the present. Event sourcing is one of the cleaner ways out.
The problem: your database is a whiteboard, not a ledger
Most SaaS applications use CRUD on top of a relational database. Create, Read, Update, Delete. When a user changes their subscription tier from Pro to Enterprise, the users table gets an UPDATE statement. The old value is gone. Unless someone had the foresight to write it to an audit table — and unless every code path that touches that record does the same — the change is invisible an hour later.
This works fine until it doesn’t. Then a customer calls and says their seat count is wrong, or their billing cycle shifted, or a feature flag flipped. You open the record. It says what it says. There is no way to know how it got that way, who changed it, when, or in what order relative to other changes. Every post-mortem devolves into stitching together application logs, Stripe webhooks, and Slack messages. Sometimes you find the answer. Often you don’t.
The instinct is to bolt on an audit log. Add a trigger. Log every write. This helps, but it is a patch on a leaky abstraction — one migration, one bulk update, one direct SQL fix from an engineer at 2am, and the audit trail has a hole in it. The problem is not that you forgot to log. The problem is that logging is optional.
The insight: invert the model
Event sourcing does not add history to your data. It makes history be your data.
The events are the source of truth. Current state is a projection — a cached summary you compute by replaying events. You cannot “forget” to log a change because there is no other way to change anything. The only write operation your system supports is append event. There is no UPDATE. There is no DELETE. Correcting a mistake means appending a compensating event, not overwriting the past.
This is the difference between a whiteboard and a bank ledger. A whiteboard shows what someone last wrote. A ledger shows every transaction, and the balance is derived. You cannot tamper with row 47 of a ledger without it being obvious that row 47 changed.
An analogy: your bank statement
Your checking account balance is $2,431.19. That number is not stored anywhere authoritative. It is the sum of every deposit, withdrawal, transfer, and fee since the account opened. If the bank’s system showed you $2,431.19 but could not tell you how it got there, you would close the account. Yet this is exactly how most SaaS databases work.
Event sourcing is your product built like the bank statement, not like the balance printout.
A minimal worked example
Say you sell a B2B SaaS product with team accounts. A customer complains: “We are being billed for 12 seats but we only have 8 users.”
In a CRUD system, you look at the accounts table. seat_count: 12. You have no idea why.
In an event-sourced system, you query the event stream for account acme-corp:
2024-01-03 AccountCreated seats=5 actor=signup
2024-02-11 SeatsAdded +3 actor=admin@acme.com
2024-04-22 UserInvited user=jane@acme.com
2024-04-22 SeatsAdded +1 actor=system (auto-provision)
2024-06-01 UserRemoved user=bob@acme.com
2024-06-01 (no seat change)
2024-07-15 SeatsAdded +3 actor=admin@acme.com
2024-08-02 UserRemoved user=carol@acme.comNow the story is obvious. Auto-provisioning added a seat when Jane was invited, but removing Bob and Carol did not decrement seats. The admin also manually added 3 seats in July, probably planning for hiring that didn’t happen. The customer is not wrong. Neither is the billing system. The policy is wrong — removals should have released seats.
You could not have reached this diagnosis from seat_count: 12. And crucially, no one had to remember to log any of this. It is how the system stores data.
How this shows up in architecture
Event sourcing usually travels with a few companion patterns. You do not have to adopt all of them, but you will hear the names.
- Event store. An append-only database of events. Can be Kafka, EventStoreDB, a Postgres table with the right constraints, or DynamoDB. The key property is that events are immutable and ordered per aggregate.
- Projections. Read-optimized views built by replaying events. Your “current seat count” is a projection. So is your dashboard, your search index, your reporting warehouse.
- CQRS (Command Query Responsibility Segregation). Writes go to the event store. Reads come from projections. The two sides scale independently.
- Event-driven architecture. Once events are first-class, other services can subscribe to them. Billing reacts to
SeatsAdded. Analytics reacts toUserInvited. This is where event driven architecture explained in the abstract starts to feel obvious in practice.
The gotchas — what event sourcing is bad at
This is not a free upgrade. Real tradeoffs:
- Schema evolution is hard. Events are immutable. If you change the shape of
SeatsAddedin v2, you still have five years of v1 events to replay. You need versioning discipline from day one. - Queries across aggregates are awkward. “Show me all accounts with more than 10 seats” is trivial in SQL. In an event-sourced system, you answer it from a projection, which someone has to build and maintain.
- GDPR and “right to be forgotten” get tricky. Immutable events plus a legal requirement to delete personal data means you need crypto-shredding, tombstone events, or careful separation of PII from event streams.
- Debugging replays is a skill. When a projection is wrong, you rebuild it from events. That is powerful but not push-button. Engineers need to understand determinism, event ordering, and idempotency.
- Overkill for simple domains. If your app is a directory of restaurants, you do not need event sourcing. You need a database.
When to use event sourcing vs when not to
Use it when:
- You handle money, credits, quotas, or anything where the sequence of changes matters legally or contractually. Fintech, billing, lending, insurance — see how event-style ledgers show up in products like GimBooks for accounting or Cashpo for lending.
- Your domain has real workflows with state transitions — orders, claims, prescriptions, shipments — where “how did we get here” is a routine business question.
- Regulators, auditors, or enterprise customers ask for a tamper-evident history.
- Multiple downstream systems need to react to the same business events without polling your database.
Do not use it when:
- Your app is mostly CRUD on independent records with no meaningful workflow — a CMS, a contact list, a simple internal tool.
- Your team has never operated an event-driven system and there is no one to own the operational complexity.
- You are still finding product-market fit and your data model changes weekly. Event sourcing rewards stable domain concepts.
A middle path worth considering: keep CRUD for most of your app, and event-source only the subsystems where history is non-negotiable — billing, entitlements, compliance-sensitive workflows. This is often what pragmatic teams end up with after they read the textbook and meet reality. Getting that boundary right is usually the harder half of a legacy modernization effort than the code itself.
The bottom line on event sourcing vs CRUD
CRUD stores what is true. Event sourcing stores what happened, and derives what is true. If your business runs on the second question more than the first — if support tickets keep asking “how did this account end up like this” and you keep not having an answer — you have outgrown CRUD in the places that matter. You do not need to rewrite everything. You need to identify the two or three aggregates where history is the product, and model those as events.
Frequently Asked Questions
Is event sourcing the same as having an audit log?
No. An audit log sits alongside your normal data and can be forgotten, bypassed, or corrupted by a bulk migration. Event sourcing makes the events themselves the primary data — there is no other way to change state, so the history cannot be turned off or skipped.
Do we have to rewrite our entire application to adopt event sourcing?
Almost never worth doing. Most teams event-source only the subsystems where history matters — billing, entitlements, order workflows, compliance — and leave the rest as CRUD. The boundary between the two is the important design decision.
How does event sourcing relate to Kafka or event-driven architecture?
Kafka and similar log-based brokers are one way to implement the event store, and event-driven architecture is what naturally emerges once events are first-class. But event sourcing is a data modeling choice, not a specific tool. You can event-source with a Postgres table if the volume is modest.
What about GDPR if events are immutable?
You handle it with techniques like crypto-shredding (encrypt personal data with a per-user key and delete the key), keeping PII out of event payloads and in a separate deletable store, or issuing tombstone events. It requires planning but is a solved problem in regulated industries.
How do we know if event sourcing is right for our product?
Look at your support tickets from the last quarter. Count how many required someone to reconstruct history that the database did not store. If that number is meaningful and growing, at least part of your system should be event-sourced. For a scoped assessment of where the boundary should sit, talk to CodeNicely for a personalized review.
Found this useful? CodeNicely publishes engineering and product playbooks weekly. Browse the archive or tell us what you're building.
_1751731246795-BygAaJJK.png)