# Events as the Source of Truth — Event-Driven Architecture & CQRS

Source: https://www.skillbyai.com/en/event-driven-architecture/s-basics

> Store state as a sequence of events and rebuild it by replaying them.

## Store what happened, derive what is

Traditional systems store **current state** and overwrite it: the account balance is 4,500, and how it got there is lost unless you log it separately. **Event sourcing** stores the **sequence of events** for each entity (an **aggregate**) as the system of record: `AccountOpened`, `MoneyDeposited 5000`, `MoneyWithdrawn 500`. Current state is computed by **replaying** (folding) the events in order. To handle a command, load the aggregate's events, rebuild its state, check business rules, and **append** new events. Appends use **optimistic concurrency**: you state the version you read, and the store rejects the append if someone else appended in the meantime. Benefits: a complete **audit trail**, the ability to answer "what was the state last Tuesday?", new read models built by replaying history, and natural integration with event-driven systems. Event stores include EventStoreDB (KurrentDB), Marten on PostgreSQL, Axon Server, or a well-designed table in a relational database.

## State as a fold over events

Each event moves the state forward; replaying the list from the start reproduces current state.

![A horizontal row of event tiles with an arrow running through them into a single summary box on the right.](assets/figures/event-driven-architecture/section-6-map.svg) — Figure 6.1 — Replaying an event stream to compute current state.

## An event-sourced bank account

State comes only from applying events; commands decide which new events to append.

```python
from dataclasses import dataclass, field

@dataclass
class Account:
    id: str
    balance: int = 0
    version: int = 0
    pending: list = field(default_factory=list)

    def apply(self, event):
        if event["type"] == "MoneyDeposited":
            self.balance += event["amount"]
        elif event["type"] == "MoneyWithdrawn":
            self.balance -= event["amount"]
        self.version += 1

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("insufficient funds")      # rule checked against replayed state
        event = {"type": "MoneyWithdrawn", "amount": amount}
        self.apply(event)
        self.pending.append(event)

def load(store, account_id):
    acct = Account(account_id)
    for e in store.read_stream(f"account-{account_id}"):
        acct.apply(e)
    return acct

acct = load(store, "a-7")
acct.withdraw(500)
store.append(f"account-a-7", acct.pending, expected_version=acct.version - len(acct.pending))
```

## Events are immutable

You never edit or delete an event to fix a mistake. You append a correcting event (`DepositReversed`), exactly as an accountant posts a reversing entry rather than erasing the ledger.

**Quiz:** How is the current state of an event-sourced aggregate obtained?

- [ ] By reading a single row that is overwritten on every change
- [ ] By querying the broker for the latest message
- [x] By replaying its events in order (optionally from a snapshot)
- [ ] By asking every consumer

*Answer:* By replaying its events in order (optionally from a snapshot). State is derived by applying the stored events in sequence.
