You’ve been there. It’s 2 AM. A production system is bleeding. Someone asks the obvious question: “What happened?” And your database — the thing you trusted as your source of truth — stares back at you with the blank eyes of a goldfish.
It knows now. It has no idea how it got here.
Your database doesn’t store truth. It stores the corpse of truth — the final state, stripped of every decision that produced it.
Every UPDATE is a small act of arson. You’re burning the past to make room for the present. And when something goes wrong, you’re left sifting through ashes, trying to reconstruct a crime scene from a body that’s already been moved.
This is the dirty secret of modern software architecture: we’ve optimized ourselves into amnesia. And then we wonder why debugging production feels like archaeology with no tools.
Martin Fowler wrote about event sourcing back in 2005. Almost two decades later, most engineers still treat it as a database pattern — a quirky alternative to CRUD that makes things “more complex” for unclear benefit. They’re missing the point entirely.
Event sourcing isn’t about how you store data. It’s about making time a first-class citizen in your system.
Here’s the shift: instead of storing the current state of an entity, you store every event that ever changed it. Not “account balance: $4,200.” But “deposited $500. Withdrew $120. Deposited $3,800. Transferred $80.” Each event is immutable, timestamped, and permanent. The current state isn’t stored — it’s derived. You replay the tape.
State is a conclusion. Events are the argument. Stop saving conclusions and start saving arguments.
Think about what this actually buys you. A customer reports that their account balance is wrong. With a traditional database, you see the wrong number. That’s it. Your investigation starts from zero — logs, timestamps, guesswork, prayer.
With event sourcing, you hit replay. You see every event in sequence. You can pause at any point in time and ask: “What did the system know at 3:47 PM on Tuesday?” You can replay a specific event and watch it ripple through your projections. You’re not debugging a database. You’re watching a story unfold — and finding the exact page where the plot broke.
This isn’t a minor improvement. It’s the difference between a doctor who sees a corpse and one who has a complete medical history.
Now, the catch — and it’s a real one. Event sourcing trades one kind of complexity for another. You now have an append-only log that grows forever. Reading the current state means replaying potentially thousands of events. Querying “give me all accounts with balance over $10,000” becomes a nightmare if you have to reconstruct every account from scratch.
So you build snapshots — periodic checkpoints that capture the current state at a moment in time. You build read models — projections that transform the event stream into queryable shapes. You deal with eventual consistency, where your read model lags slightly behind your write model. You manage schema evolution, because events are permanent but your understanding of them will change.
The paradox at the heart of event sourcing: you need both a perfect memory and a fast answer, and getting both requires maintaining two versions of reality that are always slightly out of sync.
This is where most teams either succeed or fail. The ones who fail treat the event log as their database and try to query it directly. The ones who succeed understand that event sourcing is an architecture, not a storage strategy. The event log is your source of truth. Your read models are your performance layer. They serve different masters and that’s fine.
If you’re building a system where audit trails matter — finance, healthcare, compliance, anything regulated — this isn’t optional. Regulators don’t want to know the current state. They want to know every decision that led to it. They want the story. And if you can’t produce the story, you’re not compliant. You’re just current.
If you’re building collaborative systems — think Google Docs, where multiple users edit simultaneously and you need to merge conflicts — event sourcing is how you actually solve it. Each edit is an event. The document state is a projection. Conflict resolution happens at the event level, not the state level. This is why operational transform and CRDTs are fundamentally event-sourcing patterns, even if nobody calls them that.
If you’re building IoT systems where thousands of sensors report state changes per second, storing current state is almost meaningless. The value isn’t in “the temperature is 72°.” It’s in the pattern: “the temperature spiked to 89° for 3 minutes, then dropped to 71°.” The event stream is the data. The current state is just the last frame of a movie you threw away.
Most systems don’t need to know what something is. They need to know what something went through to become what it is.
Here’s what I’d tell any architect staring down this decision: don’t adopt event sourcing because it’s elegant. Adopt it because you’ve felt the specific pain of not being able to answer “what happened?” in production. Adopt it because your business logic depends on temporal reasoning — on knowing not just what’s true now, but what was true then, and why it changed.
And if you do adopt it, commit. Half-measures kill you. A system that stores events and mutates state is the worst of both worlds — you get the complexity of event sourcing without the benefit of a trustworthy replay. Either your event log is the source of truth, or it’s decoration. There’s no middle ground.
The next time you’re designing a system, ask yourself one question: if this system were a person in a courtroom, would the jury need to see the current state — or the complete history of every decision? If it’s the latter, and for most meaningful systems it is, then you already know what to do.
State tells you where you are. Events tell you how you got there. Only one of those lets you change where you’re going.
FAQ
Q: Isn't event sourcing just over-engineering for most applications?
A: For a CRUD app with no audit requirements? Absolutely. Don't adopt it. Event sourcing earns its complexity when you need temporal queries, complete audit trails, or replay-based debugging. If you've never stared at a production bug and wished you could hit rewind, you don't need it.
Q: How do you handle the read performance problem?
A: You build projections — separate read models that are fed by the event stream and optimized for specific queries. The event log is your write-side source of truth; your projections are your read-side performance layer. They'll be eventually consistent with each other, and for most use cases, that's perfectly fine.
Q: What about schema evolution when old events are immutable?
A: This is the real hard part, not the architecture. You version your events, write upcasters that transform old events into new shapes on read, and accept that your event schema is a living contract. It's painful but solvable — and frankly, traditional databases have the same problem, they just hide it behind ALTER TABLE until it explodes.