Basic banking API (accounts + transactions) built with Hexagonal Architecture (Ports & Adapters) and Domain-Driven Design (DDD) in
Framework-agnostic business rules.
- Value Objects:
Money,AccountNumber,AccountId - Entities:
Account,LedgerEntry - Domain Errors:
InvalidMoneyError,InvalidAccountNumberError,InsufficientFundsError, etc.
Use cases + ports (no infrastructure details).
- Use cases:
CreateAccountUseCase,DepositUseCase,WithdrawUseCase,TransferUseCase,GetBalanceUseCase,GetTransactionsUseCase - Ports: repositories + UnitOfWork
- Inbound: FastAPI HTTP routes/schemas
- Outbound: SQLAlchemy repositories + UnitOfWork (SQLite/Postgres-ready)
Python.
It supports:
- Account creation with initial balance
- Deposits and withdrawals
- Transfers between accounts
- Transaction history (ledger)
- Concurrency safety (optimistic locking + retries)
Clone:
git clone git@github.com:elitonzky/zkybank.git
cd zkybank- Python 3.12+
- Poetry
Install Poetry (choose one):
# Option A (recommended): pipx
python -m pip install --user pipx
python -m pipx ensurepath
pipx install poetry# Option B: official installer
curl -sSL https://install.python-poetry.org | python3 -poetry install
cp .env.example .envBy default, the project uses SQLite at ./data/zkybank.db.
Run make help to see all targets.
Common commands:
make dev
make test
make lint
make typecheck
make clean-db
make smoke
make concurrencyFastest (recommended):
make devManual:
poetry run uvicorn zkybank.infrastructure.main:app --reloadOpen:
- Swagger UI: http://127.0.0.1:8000/docs
- OpenAPI JSON: http://127.0.0.1:8000/openapi.json
curl -X POST http://127.0.0.1:8000/accounts \
-H "Content-Type: application/json" \
-d '{
"account_number": "000001",
"initial_balance_cents": 10000,
"currency": "BRL"
}'Response example:
{
"account_id": "f6b3b5e6-1f22-4b65-9b4a-0c5c2c9e3d34",
"account_number": "000001",
"balance_cents": 10000,
"currency": "BRL"
}curl -X POST http://127.0.0.1:8000/accounts/000001/deposit \
-H "Content-Type: application/json" \
-d '{ "amount_cents": 500, "currency": "BRL" }'curl -X POST http://127.0.0.1:8000/accounts/000001/withdraw \
-H "Content-Type: application/json" \
-d '{ "amount_cents": 200, "currency": "BRL" }'curl -X POST http://127.0.0.1:8000/transfers \
-H "Content-Type: application/json" \
-d '{
"from_account_number": "000001",
"to_account_number": "000002",
"amount_cents": 500,
"currency": "BRL"
}'Response example:
{
"correlation_id": "bb7f7eb8-956a-4c84-a44d-ea4df70a0fd4",
"from_account_number": "000001",
"to_account_number": "000002",
"from_balance_cents": 9500,
"to_balance_cents": 500,
"currency": "BRL"
}curl http://127.0.0.1:8000/accounts/000001/balanceResponse:
{
"account_number": "000001",
"balance_cents": 9500,
"currency": "BRL"
}curl http://127.0.0.1:8000/accounts/000001/transactionsResponse example:
[
{
"entry_id": "776d86af-3cfb-4bdd-b455-9a2dc52732a8",
"entry_type": "TRANSFER_OUT",
"amount_cents": 500,
"currency": "BRL",
"correlation_id": "bb7f7eb8-956a-4c84-a44d-ea4df70a0fd4",
"occurred_at": "2026-01-12T21:13:49.684949Z",
"counterparty_account_number": "000002"
},
{
"entry_id": "23ecd42c-93aa-42e3-a4d4-8ffea62e69e2",
"entry_type": "DEPOSIT",
"amount_cents": 10000,
"currency": "BRL",
"correlation_id": null,
"occurred_at": "2026-01-12T21:13:43.253546Z",
"counterparty_account_number": null
}
]counterparty_account_number is populated for transfer entries (TRANSFER_IN / TRANSFER_OUT) so you can see who sent/received.
This project uses optimistic locking on accounts:
accounts.versionis incremented on every update.- SQLAlchemy is configured with
version_id_col, so updates include the expected version. - If another transaction already updated the row, SQLAlchemy raises a concurrency error.
- Use cases (
deposit,withdraw,transfer) retry a few times and log conflicts.
Notes about SQLite:
- SQLite uses coarse-grained locking for writes, so heavy stress tests may hit
database is locked.
Runs the required concurrency scenarios (in parallel) and asserts final balances:
make concurrencyCreates tables, runs a few operations, and prints the results:
make smokemake test
# or
poetry run pytest -qEnable git hooks locally:
poetry run pre-commit install
poetry run pre-commit run --all-filesEliton Jorge Email: eliton-jorge@hotmail.com LinkedIn: https://www.linkedin.com/in/eliton-jorge-zky/