Event Sourcing

Level: Advanced 60–90 min

Concepts: Design PatternsStateBusiness LogicBoundaries


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.