पाठ 16 / 25
Events as the Source of Truth
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.
An event-sourced bank account
State comes only from applying events; commands decide which new events to append.
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.
त्वरित जाँच: 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
- 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.