Banking Demo — Money Transfers with Compensation
Event-sourced account management with two-phase transfers, saga-based compensation, and a full transaction audit trail.
Source Code:
bases/orchestrix/banking_demo/
Quick Start
What It Demonstrates
- Event-sourced accounts — balance reconstructed from events
- Transfer saga — two-phase debit/credit with automatic rollback
- Compensation — if the credit leg fails, the debit is reversed
- Account lifecycle — open, suspend, reactivate, close
- Command handlers —
BankingCommandHandlerswires everything together
Domain Model
Commands
| Command | Fields | Description |
|---|---|---|
OpenAccount |
account_id, owner_name, initial_balance |
Open a new account |
DepositMoney |
account_id, amount, description |
Deposit funds |
WithdrawMoney |
account_id, amount, description |
Withdraw funds |
TransferMoney |
transfer_id, from_account_id, to_account_id, amount, description |
Transfer between accounts |
SuspendAccount |
account_id, reason |
Suspend an account |
ReactivateAccount |
account_id |
Reactivate a suspended account |
CloseAccount |
account_id |
Close an account |
Events
| Event | Description |
|---|---|
AccountOpened |
Account created with initial balance |
MoneyDeposited |
Funds added |
MoneyWithdrawn |
Funds removed |
AccountSuspended |
Account suspended |
AccountReactivated |
Account reactivated |
AccountClosed |
Account closed |
TransferInitiated |
Transfer started by saga |
TransferDebited |
Source account debited |
TransferCompleted |
Transfer succeeded |
TransferFailed |
Transfer failed after compensation |
TransferReversed |
Compensation: debit reversed |
Aggregate: Account
from orchestrix.core.eventsourcing.aggregate import AggregateRoot
@dataclass
class Account(AggregateRoot):
owner_name: str = ""
balance: Decimal = Decimal("0")
status: AccountStatus = AccountStatus.ACTIVE # ACTIVE | SUSPENDED | CLOSED
transactions: list = field(default_factory=list)
Key methods: open(), deposit(), withdraw(), suspend(), reactivate(), close().
Business rules: no overdraft, no ops on suspended/closed accounts, no closing with non-zero balance.
Transfer Saga Flow
TransferMoney command
↓
TransferInitiated → TransferSaga
↓
WithdrawMoney (debit source)
↓
TransferDebited → TransferSaga
↓
DepositMoney (credit destination)
↓ success ↓ failure
TransferCompleted ✅ DepositMoney (re-credit source)
↓
TransferReversed → TransferFailed ❌
Code Structure
bases/orchestrix/banking_demo/
├── main.py # Runnable entry point (async demo)
├── models.py # Commands, Events, Enums
├── aggregate.py # Account aggregate
├── handlers.py # BankingCommandHandlers
├── saga.py # TransferSaga
└── README.md
Usage Example
import asyncio
from decimal import Decimal
from orchestrix.core.eventsourcing.aggregate import AggregateRepository
from orchestrix.infrastructure.memory.store import InMemoryEventStore
from bases.orchestrix.banking_demo.aggregate import Account
from bases.orchestrix.banking_demo.models import OpenAccount, DepositMoney
async def main():
store = InMemoryEventStore()
repo = AggregateRepository(event_store=store)
# Open account
agg = Account()
agg.open(OpenAccount(account_id="acc-1", owner_name="Alice", initial_balance=Decimal("1000")))
repo.save(agg)
# Deposit
agg = repo.load(Account, "acc-1")
agg.deposit(DepositMoney(account_id="acc-1", amount=Decimal("500"), description="Bonus"))
repo.save(agg)
print(f"Balance: {agg.balance}") # 1500
asyncio.run(main())
Related
- E-Commerce Demo — Multi-aggregate saga for order processing
- Notifications Demo — Retry logic and error handling
- Event Store Guide — Persistence deep-dive