Event Sourcing: Store the Events, Derive the State

May 4, 2026


A traditional CRUD service stores the current state of an order. An event-sourced service stores every change that ever happened to it, and computes the current state on demand.

The shift is small to describe and large to live with. Instead of writing UPDATE orders SET status = 'shipped', you append a row to an event log: OrderShipped(orderId, ts, carrier). The log is append-only. Nothing is ever updated, nothing is ever deleted. To answer "what is this order today," you load every event for that aggregate and fold them into a state object. The state is a function of the history.

The upside is enormous. You get a full audit trail by construction, because the trail is the database. You can answer questions you never planned for, by writing a new projection that consumes the same log: a fraud model, a billing report, an analytics view. Each projection is a separate read model, optimized for the queries it serves. Bug in a projection? Drop the table and replay the log to rebuild it. The events are immutable truth; the read side is disposable.

The downside is that immutability cuts both ways. Events live forever, so schema evolution is forever. You add a field to OrderShipped today. Three years from now your replay code still has to handle the version without that field, and the version with the wrong currency unit, and the version that misspelled the carrier name. You version events and write upcasters that translate old shapes into current ones. Delete the upcasters and old projections stop building.

The production failure mode that surprises every team eventually is the replay that does not finish. A new projection seems fine in staging on a sample of events. In production you point it at the real log: ten million events for one aggregate type, foreign key lookups for each, an hour of cold cache. The rebuild takes six hours and pins a database the entire time. By the time it catches up, fresh events have arrived and you are still behind.

The fix is snapshots. Periodically write a serialized state for each aggregate at a known event offset. To rehydrate, load the latest snapshot and replay only the events that came after. Snapshots are themselves derivable, so corrupt ones can be discarded. With them, replay becomes minutes, not hours.

Event sourcing pairs with CQRS naturally, because the write side is already a log and the read side is already separate. Pick it when audit, time-travel, or multiple read shapes justify the operational tax. Otherwise CRUD is fine.

Key takeaway

Events are the source of truth. Projections are disposable views you can rebuild. The state on disk is just a cache of the log.

Originally posted on LinkedIn. View original.


All Rights Reserved.