# Event Sourcing Pattern

## Problem

Traditional systems store only the current state of entities. This loses the history of how the state was reached, makes auditing impossible, prevents temporal queries ("what was the balance last Tuesday?"), and makes debugging state corruption extremely difficult.

## Solution

**Event Sourcing** persists the state of an entity as a sequence of state-changing events. Instead of storing the current state, the system stores every event that has occurred. The current state is derived by replaying all events from the beginning (or from a snapshot). Events are immutable and append-only.

## When to Use

- Full audit trail is a business requirement (finance, healthcare, legal).
- You need temporal queries or the ability to reconstruct past states.
- Combined with CQRS for high-performance reads alongside event-based writes.
- Complex domain where understanding "how we got here" matters.

## When to Avoid

- Simple CRUD where history is not needed.
- High-frequency state changes where event volume would be unmanageable.
- When the team lacks experience with eventual consistency.
- Domains where "right to be forgotten" (GDPR) makes immutable logs problematic.

## Pseudocode

```
CLASS EventStore:
    events = []

    METHOD append(event):
        events.add(event)

    METHOD get_events(entity_id):
        RETURN events WHERE entity_id matches

CLASS BankAccount:
    balance = 0
    events = []

    METHOD apply(event):
        IF event.type == "deposited":
            balance += event.amount
        IF event.type == "withdrawn":
            balance -= event.amount

    STATIC METHOD from_events(events):
        account = new BankAccount()
        FOR event IN events:
            account.apply(event)
        RETURN account
```

## Python

```python
from dataclasses import dataclass, field
from typing import Any


@dataclass(frozen=True)
class Event:
    entity_id: str
    event_type: str
    data: dict[str, Any]
    version: int


class EventStore:
    def __init__(self) -> None:
        self._events: list[Event] = []

    def append(self, event: Event) -> None:
        self._events.append(event)
        print(f"    [Store] Appended: {event.event_type} v{event.version} -> {event.data}")

    def get_events(self, entity_id: str) -> list[Event]:
        return [e for e in self._events if e.entity_id == entity_id]

    def all_events(self) -> list[Event]:
        return list(self._events)


class BankAccount:
    def __init__(self, account_id: str) -> None:
        self.account_id = account_id
        self.balance: float = 0.0
        self.is_open: bool = False
        self._version: int = 0
        self._pending_events: list[Event] = []

    def _apply(self, event: Event) -> None:
        if event.event_type == "AccountOpened":
            self.is_open = True
            self.balance = event.data.get("initial_balance", 0.0)
        elif event.event_type == "MoneyDeposited":
            self.balance += event.data["amount"]
        elif event.event_type == "MoneyWithdrawn":
            self.balance -= event.data["amount"]
        elif event.event_type == "AccountClosed":
            self.is_open = False
        self._version = event.version

    def _raise_event(self, event_type: str, data: dict[str, Any]) -> None:
        self._version += 1
        event = Event(self.account_id, event_type, data, self._version)
        self._apply(event)
        self._pending_events.append(event)

    def open(self, initial_balance: float = 0.0) -> None:
        self._raise_event("AccountOpened", {"initial_balance": initial_balance})

    def deposit(self, amount: float) -> None:
        if not self.is_open:
            raise ValueError("Account is not open")
        self._raise_event("MoneyDeposited", {"amount": amount})

    def withdraw(self, amount: float) -> None:
        if not self.is_open:
            raise ValueError("Account is not open")
        if amount > self.balance:
            raise ValueError(f"Insufficient funds: {self.balance} < {amount}")
        self._raise_event("MoneyWithdrawn", {"amount": amount})

    def close(self) -> None:
        self._raise_event("AccountClosed", {})

    def flush_events(self) -> list[Event]:
        events = self._pending_events[:]
        self._pending_events.clear()
        return events

    @staticmethod
    def from_events(account_id: str, events: list[Event]) -> "BankAccount":
        account = BankAccount(account_id)
        for event in events:
            account._apply(event)
        return account


def main() -> None:
    store = EventStore()

    print("--- Create and use account ---")
    account = BankAccount("ACC-001")
    account.open(100.0)
    account.deposit(50.0)
    account.deposit(25.0)
    account.withdraw(30.0)

    # Persist pending events
    for event in account.flush_events():
        store.append(event)

    print(f"\n  Current balance: ${account.balance:.2f}")
    print(f"  Version: {account._version}")

    print("\n--- Reconstruct from event store ---")
    history = store.get_events("ACC-001")
    rebuilt = BankAccount.from_events("ACC-001", history)
    print(f"  Rebuilt balance: ${rebuilt.balance:.2f}")
    print(f"  Rebuilt version: {rebuilt._version}")
    print(f"  Is open: {rebuilt.is_open}")

    print("\n--- Replay to version 2 (after open + first deposit) ---")
    partial = BankAccount.from_events("ACC-001", history[:2])
    print(f"  Balance at v2: ${partial.balance:.2f}")

    print("\n--- Full event log ---")
    for e in store.all_events():
        print(f"  v{e.version}: {e.event_type} {e.data}")


if __name__ == "__main__":
    main()
```

**Output:**
```
--- Create and use account ---
    [Store] Appended: AccountOpened v1 -> {'initial_balance': 100.0}
    [Store] Appended: MoneyDeposited v2 -> {'amount': 50.0}
    [Store] Appended: MoneyDeposited v3 -> {'amount': 25.0}
    [Store] Appended: MoneyWithdrawn v4 -> {'amount': 30.0}

  Current balance: $145.00
  Version: 4

--- Reconstruct from event store ---
  Rebuilt balance: $145.00
  Rebuilt version: 4
  Is open: True

--- Replay to version 2 (after open + first deposit) ---
  Balance at v2: $150.00

--- Full event log ---
  v1: AccountOpened {'initial_balance': 100.0}
  v2: MoneyDeposited {'amount': 50.0}
  v3: MoneyDeposited {'amount': 25.0}
  v4: MoneyWithdrawn {'amount': 30.0}
```

## Go

```go
package main

import "fmt"

// --- Event ---

type Event struct {
	EntityID  string
	EventType string
	Data      map[string]float64
	Version   int
}

// --- Event Store ---

type EventStore struct {
	events []Event
}

func (s *EventStore) Append(event Event) {
	s.events = append(s.events, event)
	fmt.Printf("    [Store] Appended: %s v%d -> %v\n", event.EventType, event.Version, event.Data)
}

func (s *EventStore) GetEvents(entityID string) []Event {
	var result []Event
	for _, e := range s.events {
		if e.EntityID == entityID {
			result = append(result, e)
		}
	}
	return result
}

// --- Bank Account ---

type BankAccount struct {
	AccountID string
	Balance   float64
	IsOpen    bool
	version   int
	pending   []Event
}

func NewBankAccount(id string) *BankAccount {
	return &BankAccount{AccountID: id}
}

func (a *BankAccount) apply(event Event) {
	switch event.EventType {
	case "AccountOpened":
		a.IsOpen = true
		a.Balance = event.Data["initial_balance"]
	case "MoneyDeposited":
		a.Balance += event.Data["amount"]
	case "MoneyWithdrawn":
		a.Balance -= event.Data["amount"]
	case "AccountClosed":
		a.IsOpen = false
	}
	a.version = event.Version
}

func (a *BankAccount) raiseEvent(eventType string, data map[string]float64) {
	a.version++
	event := Event{a.AccountID, eventType, data, a.version}
	a.apply(event)
	a.pending = append(a.pending, event)
}

func (a *BankAccount) Open(initialBalance float64) {
	a.raiseEvent("AccountOpened", map[string]float64{"initial_balance": initialBalance})
}

func (a *BankAccount) Deposit(amount float64) {
	a.raiseEvent("MoneyDeposited", map[string]float64{"amount": amount})
}

func (a *BankAccount) Withdraw(amount float64) {
	if amount > a.Balance {
		fmt.Println("    Insufficient funds!")
		return
	}
	a.raiseEvent("MoneyWithdrawn", map[string]float64{"amount": amount})
}

func (a *BankAccount) FlushEvents() []Event {
	events := make([]Event, len(a.pending))
	copy(events, a.pending)
	a.pending = nil
	return events
}

func FromEvents(accountID string, events []Event) *BankAccount {
	a := NewBankAccount(accountID)
	for _, e := range events {
		a.apply(e)
	}
	return a
}

func main() {
	store := &EventStore{}

	fmt.Println("--- Create and use account ---")
	account := NewBankAccount("ACC-001")
	account.Open(100.0)
	account.Deposit(50.0)
	account.Deposit(25.0)
	account.Withdraw(30.0)

	for _, event := range account.FlushEvents() {
		store.Append(event)
	}

	fmt.Printf("\n  Current balance: $%.2f\n", account.Balance)
	fmt.Printf("  Version: %d\n", account.version)

	fmt.Println("\n--- Reconstruct from event store ---")
	history := store.GetEvents("ACC-001")
	rebuilt := FromEvents("ACC-001", history)
	fmt.Printf("  Rebuilt balance: $%.2f\n", rebuilt.Balance)
	fmt.Printf("  Rebuilt version: %d\n", rebuilt.version)
	fmt.Printf("  Is open: %v\n", rebuilt.IsOpen)

	fmt.Println("\n--- Replay to version 2 ---")
	partial := FromEvents("ACC-001", history[:2])
	fmt.Printf("  Balance at v2: $%.2f\n", partial.Balance)

	fmt.Println("\n--- Full event log ---")
	for _, e := range store.events {
		fmt.Printf("  v%d: %s %v\n", e.Version, e.EventType, e.Data)
	}
}
```

**Output:**
```
--- Create and use account ---
    [Store] Appended: AccountOpened v1 -> map[initial_balance:100]
    [Store] Appended: MoneyDeposited v2 -> map[amount:50]
    [Store] Appended: MoneyDeposited v3 -> map[amount:25]
    [Store] Appended: MoneyWithdrawn v4 -> map[amount:30]

  Current balance: $145.00
  Version: 4

--- Reconstruct from event store ---
  Rebuilt balance: $145.00
  Rebuilt version: 4
  Is open: true

--- Replay to version 2 ---
  Balance at v2: $150.00

--- Full event log ---
  v1: AccountOpened map[initial_balance:100]
  v2: MoneyDeposited map[amount:50]
  v3: MoneyDeposited map[amount:25]
  v4: MoneyWithdrawn map[amount:30]
```

## Related Patterns

- **CQRS** — Event Sourcing is the natural write-side persistence for CQRS. Events are stored; read models are projected from them.
- **Observer** — Observers consume events to build projections, trigger side effects, and notify external systems.
- **Memento** — Event Sourcing uses the same concept as Memento (capture state changes) but at the architectural level and with an append-only log.
- **Saga** — Sagas consume domain events to coordinate long-running transactions across multiple aggregates.
- **Specification** — Used to filter events when replaying or projecting.
