image de chargement
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 January 9, 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.

Themoneyisalreadyonthetable.

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