PeakLab
Back to glossary

Event Sourcing

Architectural pattern storing application state as a sequence of immutable events, enabling full audit trails and temporal reconstruction.

Updated on March 30, 2026

Event Sourcing is an architectural pattern that persists application state not as a single snapshot, but as a chronological sequence of immutable events. Every change is captured as a descriptive event that records what happened. This approach transforms the database into a complete audit log, where the current state is derived by replaying all historical events.

Fundamentals of Event Sourcing

  • Immutable events: each state change is recorded as an historical fact that cannot be modified
  • Source of truth: the event store becomes the authoritative source, with current state being a calculated projection
  • Complete traceability: preservation of full history with temporal context and causality of changes
  • Temporal reconstruction: ability to rebuild system state at any point in the past

Strategic benefits

  • Audit and compliance: immutable history meeting regulatory requirements (GDPR, SOX, finance)
  • Advanced debugging: exact bug reproduction by replaying events under identical conditions
  • Multiple projections: creation of materialized views optimized for different use cases from the same events
  • Model evolution: adding new features by replaying history with enriched logic
  • Read scalability: natural CQRS separation with asynchronous projection replication

Practical example: Banking system

Consider a bank account where instead of storing only the current balance, we record each transaction as an event:

bank-account-events.ts
// Event definitions
interface AccountEvent {
  eventId: string;
  accountId: string;
  timestamp: Date;
  version: number;
}

interface AccountOpened extends AccountEvent {
  type: 'AccountOpened';
  initialDeposit: number;
  currency: string;
}

interface MoneyDeposited extends AccountEvent {
  type: 'MoneyDeposited';
  amount: number;
  source: string;
}

interface MoneyWithdrawn extends AccountEvent {
  type: 'MoneyWithdrawn';
  amount: number;
  destination: string;
}

// Aggregate reconstructing state
class BankAccount {
  private balance: number = 0;
  private status: 'open' | 'closed' = 'open';
  private version: number = 0;
  private id: string = '';

  // State reconstruction via replay
  rehydrate(events: AccountEvent[]): void {
    events.forEach(event => this.apply(event));
  }

  private apply(event: AccountEvent): void {
    switch (event.type) {
      case 'AccountOpened':
        this.balance = event.initialDeposit;
        this.status = 'open';
        break;
      case 'MoneyDeposited':
        this.balance += event.amount;
        break;
      case 'MoneyWithdrawn':
        this.balance -= event.amount;
        break;
    }
    this.version = event.version;
  }

  // Command generating an event
  withdraw(amount: number): MoneyWithdrawn {
    if (this.balance < amount) {
      throw new Error('Insufficient funds');
    }
    return {
      type: 'MoneyWithdrawn',
      eventId: crypto.randomUUID(),
      accountId: this.id,
      timestamp: new Date(),
      version: this.version + 1,
      amount,
      destination: 'ATM'
    };
  }

  getBalance(): number {
    return this.balance;
  }
}

Technical implementation

  1. Model business events: identify meaningful immutable facts (past tense naming: 'OrderPlaced', 'PaymentProcessed')
  2. Choose an event store: specialized database (EventStoreDB, Apache Kafka) or adapt existing DB (PostgreSQL with append-only tables)
  3. Implement aggregates: business objects reconstructing their state by replaying events with optimistic concurrency control
  4. Create projections: materialized views computed asynchronously for queries, updated via event handlers
  5. Manage snapshots: periodic state saves to accelerate loading of large aggregates
  6. Setup versioning: event evolution strategy (upcasting) for backward compatibility

Architecture tip

Combine Event Sourcing with CQRS to clearly separate writes (events) from reads (projections). Use snapshots every N events (e.g., 100) to avoid replaying thousands of events on each load. Implement a projection rebuild mechanism to regenerate views in case of bugs or feature additions.

Associated tools and frameworks

  • EventStoreDB: specialized database for Event Sourcing with native projections
  • Axon Framework: comprehensive Java/Spring framework for CQRS and Event Sourcing
  • Marten: .NET library transforming PostgreSQL into an event store with LINQ projections
  • Apache Kafka: distributed streaming platform usable as persistent event log
  • Eventsauce: lightweight PHP library for Event Sourcing with code generator
  • Equinox: F#/.NET toolkit for Event Sourcing with CosmosDB and DynamoDB support

Event Sourcing delivers significant business value in domains requiring traceability, compliance, and temporal analysis (finance, e-commerce, healthcare). While more complex than traditional CRUD architecture, it offers exceptional evolution flexibility and naturally solves complex synchronization and audit problems. The initial investment pays off particularly for long-lived systems with strict regulatory requirements.

Let's talk about your project

Need expert help on this topic?

Our team supports you from strategy to production. Let's chat 30 min about your project.

The money is already on the table.

In 1 hour, discover exactly how much you're losing and how to recover it.

Web development, automation & AI agency

[email protected]
Newsletter

Get our tech and business tips delivered straight to your inbox.

Follow us
Crédit d'Impôt Innovation - PeakLab agréé CII

© PeakLab 2026