Event Sourcing
Level: Advanced 60–90 minConcepts: 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
- An account must be opened before any other operation
- Deposits must be positive amounts
- Withdrawals must be positive amounts and cannot exceed the current balance
- A closed account cannot accept deposits or withdrawals
- An account can only be closed if the balance is zero
- 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
| Events | Expected 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 $150 | Error: insufficient funds |
| Open, Close | Error: account already closed (on next operation) |
| Deposit without Open | Error: account not found |
| Open, Deposit $100, Close | Error: balance must be zero |
| Open, Deposit $100, Withdraw $100, Close | OK |
Temporal queries:
| Events (with timestamps) | Query | Result |
|---|---|---|
| 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.