Event Sourcinginfrastructure

Why Event Sourcing

Store what happened, derive what is

v1.1·12 min read·Kenneth Pernyér
event-sourcingarchitecturedistributed-systemsauditcqrs

The Problem

Traditional databases store current state. When you update a record, the previous state is lost. This creates fundamental problems for business systems:

You can't answer "what happened?"

An account balance is $1,000. How did it get there? What transactions occurred? Were any reversed? Traditional CRUD databases only know current values, not history.

Audit requirements demand history. Debugging requires understanding sequences of events. Business intelligence needs to analyze patterns over time. CRUD gives you a snapshot; you need a movie.

We needed a data model that:

  • Preserves complete history by design
  • Enables point-in-time reconstruction
  • Supports audit and compliance requirements
  • Allows evolving interpretations of the same data

Current Options

OptionProsCons
CRUD with Audit TablesTraditional database with separate audit logging.
  • Familiar development model
  • Existing tooling works
  • Simple queries for current state
  • Audit tables are often incomplete
  • Two sources of truth diverge
  • Reconstruction is complex
  • Schema changes break audit history
Event SourcingStore events as source of truth; derive state.
  • Complete audit trail by design
  • Point-in-time reconstruction
  • Events are immutable
  • Enables temporal queries
  • Different programming model
  • Event schema evolution requires care
  • Eventually consistent by nature
  • Tooling less mature than CRUD
Change Data CaptureCapture changes from traditional database as events.
  • Works with existing databases
  • Downstream systems get event streams
  • Gradual adoption possible
  • Events are database-centric, not domain-centric
  • Doesn't capture intent
  • Still lose data on destructive updates
  • Added infrastructure complexity

Future Outlook

Event sourcing is becoming standard for business-critical systems.

The audit requirement is universal now.

Regulations require knowing who did what when. Business intelligence requires historical analysis. AI training requires event sequences. The need for history is everywhere.

Event sourcing isn't just about compliance—it's about capability. When you have the full history, you can build features that CRUD can't support: undo, time travel debugging, what-if analysis, pattern detection.

The tooling is maturing. Event stores like EventStoreDB, Kafka with proper event schemas, and cloud-native solutions make implementation easier. The pattern is moving from "advanced technique" to "standard practice."

Our Decision

Why we chose this

  • Complete audit trailEvery change is preserved; nothing is lost
  • Point-in-time queriesReconstruct state at any historical moment
  • Debugging superpowersReplay events to understand how state evolved
  • Business flexibilityReinterpret historical events with new business logic

×Trade-offs we accept

  • Learning curveDifferent mental model than CRUD
  • Event versioningSchema changes require migration strategies
  • Eventual consistencyProjections may lag behind events

Motivation

For a system that handles real business decisions—obligations, transactions, approvals—"trust us, the current state is correct" isn't acceptable.

Event sourcing gives us an immutable record of everything that happened. We can prove the sequence of events that led to any state. We can replay history to debug issues. We can rebuild projections when business logic changes.

This isn't overhead—it's the foundation of a trustworthy system. When a customer questions a transaction, we don't guess; we show them the exact sequence of events.

Recommendation

Start with event sourcing for new aggregates, especially where:

  • Audit requirements exist
  • Business logic changes frequently
  • Multiple views of the same data are needed

Use a proven event store (EventStoreDB, Kafka) rather than building on a general database. The concurrency and ordering guarantees matter.

Design events around business intent, not data changes:

  • Good: InvoiceSent, PaymentReceived, InvoiceDisputed
  • Bad: InvoiceStatusUpdated, BalanceChanged

Plan for event versioning from day one. Events are immutable; schemas must evolve gracefully.

Examples

domain/invoice/events.tstypescript
// Domain events capture business intent
type InvoiceEvent =
  | { type: 'InvoiceCreated'; invoiceId: string; customerId: string; amount: number; dueDate: string }
  | { type: 'InvoiceSent'; invoiceId: string; sentAt: string; sentTo: string }
  | { type: 'PaymentReceived'; invoiceId: string; amount: number; receivedAt: string }
  | { type: 'InvoiceDisputed'; invoiceId: string; reason: string; disputedAt: string }
  | { type: 'DisputeResolved'; invoiceId: string; resolution: 'refunded' | 'upheld'; resolvedAt: string };

// Reconstruct state by folding events
function reconstructInvoice(events: InvoiceEvent[]): Invoice {
  return events.reduce((state, event) => {
    switch (event.type) {
      case 'InvoiceCreated':
        return { ...state, id: event.invoiceId, amount: event.amount, status: 'draft' };
      case 'InvoiceSent':
        return { ...state, status: 'sent', sentAt: event.sentAt };
      case 'PaymentReceived':
        return { ...state, status: 'paid', paidAmount: (state.paidAmount ?? 0) + event.amount };
      case 'InvoiceDisputed':
        return { ...state, status: 'disputed', disputeReason: event.reason };
      case 'DisputeResolved':
        return { ...state, status: event.resolution === 'refunded' ? 'refunded' : 'paid' };
      default:
        return state;
    }
  }, {} as Invoice);
}

Events describe what happened in business terms. State is derived by replaying events. The event log is the source of truth.

Related Articles

Stockholm, Sweden

Version 1.1

Kenneth Pernyér signature