Event Sourcing

Level: Advanced 60–90 min

Concepts: Design PatternsStateBusiness LogicBoundaries

Solutions: C# | TypeScript | Python


Build a simple bank account using event sourcing — where the current state is derived entirely from replaying a sequence of events.

Requirements

Events

The system supports these events:

  • AccountOpened { accountId, ownerName, timestamp }
  • MoneyDeposited { accountId, amount, timestamp }
  • MoneyWithdrawn { accountId, amount, timestamp }
  • AccountClosed { accountId, timestamp }

Rules

  1. An account must be opened before any other operation
  2. Deposits must be positive amounts
  3. Withdrawals must be positive amounts and cannot exceed the current balance
  4. A closed account cannot accept deposits or withdrawals
  5. An account can only be closed if the balance is zero
  6. The current balance is calculated by replaying all events in order

Projections

Given a list of events, produce:

  • Current balance — sum of deposits minus withdrawals
  • Transaction history — list of all deposits and withdrawals with running balance
  • Account summary — owner name, current balance, number of transactions, account status (open/closed)

Temporal Queries

  • Balance at a point in time — given a timestamp, what was the balance at that moment?
  • Transactions in a date range — filter the event stream by timestamp

Test Cases

EventsExpected Balance
Open, Deposit $100$100
Open, Deposit $100, Deposit $50$150
Open, Deposit $100, Withdraw $30$70
Open, Deposit $100, Withdraw $100$0
Open, Deposit $100, Withdraw $150Error: insufficient funds
Open, CloseError: account already closed (on next operation)
Deposit without OpenError: account not found
Open, Deposit $100, CloseError: balance must be zero
Open, Deposit $100, Withdraw $100, CloseOK

Temporal queries:

Events (with timestamps)QueryResult
Open (T1), Deposit $100 (T2), Deposit $50 (T3)Balance at T2$100
Open (T1), Deposit $100 (T2), Withdraw $30 (T3)Transactions T1–T2[Deposit $100]

Bonus

  • Add MoneyTransferred { fromAccountId, toAccountId, amount, timestamp } — transfers between two accounts
  • Implement snapshots — periodically save the computed state so replay doesn’t need to start from the beginning
  • Add an event store that persists events and supports querying by account ID
  • Implement undo — reverse the last event (only if it’s the most recent)

Hint

Start with the simplest projection: replaying events to get a current balance. Get that working with just Open and Deposit before adding withdrawals. The validation rules (insufficient funds, closed account) are separate from the projection — test them independently.

Reference Walkthrough

Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/event-sourcing with twenty-four scenarios across all three languages, an EventBuilder for constructing typed events with sensible defaults, and an AccountBuilder that composes event streams and rebuilds aggregates fluently. Domain exceptions (AccountNotOpenException, AccountClosedException, InsufficientFundsException, InvalidAmountException, NonZeroBalanceException) name every rejection. Temporal queries (balance-at-a-point-in-time, transactions-in-range) demonstrate the power of replaying an immutable event stream.

This kata ships in middle gear — one commit per language with the full event-sourcing design. See the repo’s Gears section for why that’s a deliberate teaching choice.


  • The Test Pyramid Was an Economic Argument
    The test pyramid was not a quality law. It was a cost structure: unit tests were cheap, integration tests were expensive, so you wrote many of the first and few of the second. Agents collapsed the cost of writing tests at every level, and the cheapest test that still tells the truth is the one that pins a seam the agent cannot fake.
  • Agents Amplify Whatever Vocabulary They Find
    Agents do not bring vocabulary to your codebase. They mirror what's already there. Strong vocabulary gets agent contributions that read like the team. Weak vocabulary gets faster mediocrity. The work you did for craft reasons just became leverage.
  • The Hidden Output of TDD Was Never Code
    The visible artifact of TDD is a passing test suite. The actual artifact, the one that compounded value over the life of the codebase, was the vocabulary the team built while writing it. The dictionary, not the regression net.