Events and messaging

3 min read·Published ·Updated

Event Sourcing: When the History Matters More Than the Latest Row

Keeping a history does not automatically require Event Sourcing. The stronger requirement is rebuilding state from events while preserving their meaning over time.

A well-designed audit log can usually answer who changed an order total. That requirement alone does not justify converting the product to Event Sourcing. The stronger requirement is that the state of a chosen part of the system can be reconstructed from its recorded events.

Inventory movements offer a useful illustration. Receipt, reservation, release and dispatch represent different business facts. Their history can support stock balances and other views. But keeping a warehouse journal does not, by itself, establish Event Sourcing: the important distinction is what the application treats as its source of state.

Which record is authoritative?

A conventional application may treat the current stock row as primary and keep an additional history for inspection. With Event Sourcing, the event stream is primary and applying it produces state. If rebuilding still needs another table containing unrecorded business facts, the reconstruction promise is incomplete.

A snapshot stores state at a particular stream position so the application can apply only later events. Screens and reports may use separate projections. Snapshots accelerate access; they do not replace the authoritative stream or its backups.

This is not synonymous with event-driven architecture. A service can publish messages without storing its own model as events. The distinction between recording history and rebuilding state is discussed in the Event Sourcing reference.

Do not reconstruct yesterday using today's decisions

Suppose a sale included a discount. Replaying it by querying today's price list and rerunning today's discount rule may produce a different result. Preserve enough facts about the original decision: the amount used, the rule version or the relevant inputs, according to the model.

Event formats evolve too. An old field must not silently acquire a new meaning. Support historical versions or explicitly transform their representation without changing the underlying fact. A correction to a business fact usually becomes another event.

Be deliberate about personal information in the stream. Unnecessary names, addresses or other details make later correction, access control and deletion harder. Data-lifecycle requirements belong in the storage design, not in a discussion after years of history have accumulated.

Two writers can still reserve the same stock

Append-only storage does not remove concurrency problems. Two handlers can read the same state and both decide inventory is available. Appending an event needs a check that the stream has not changed since the decision was made. On a conflict, reload and reconsider the operation.

That protects a particular stream, not an automatic transaction spanning every stock item, payment and supplier. The Azure Architecture Center guide discusses concurrency, snapshots and derived views.

Rebuilding a report must not repeat a shipment

Replay should rebuild a representation, not send old emails or ask the carrier to ship again. Keep reconstruction separate from external effects. Account for newly arriving events so that switching to a rebuilt view creates neither gaps nor duplicates.

Before committing to the design, run a small experiment in one domain: discard a derived view in a test environment, reconstruct it and compare the result. Include historical event versions and verify that no external actions repeat. If the business does not need that capability and mainly wants to investigate changes, start by assessing audit logging and record versioning instead. They need not change how the entire application works.