Welcome to the Multiplayer Banking Kata! In this session, you and your pairing partner will build a resilient banking node in Kotlin, practice Outside-In TDD and Object Calisthenics, and connect your node to a live inter-bank SWIFT network alongside all other workshop participants.
- JDK 25 (configured via
.sdkmanrc) - Node.js (for
npx localtunnel) orngrok
From the repository root:
./gradlew testAll unit tests in :bank-starter should run and pass in ~1-2 seconds.
In a terminal tab, expose your local port 8080 to the network / internet:
npx localtunnel --port 8080Note the public HTTPS URL printed (e.g., https://brave-fox-42.loca.lt).
Visit the registration page on the facilitator's SWIFT Hub:
- Registration URL:
http://<FACILITATOR_IP>:9000/register(or scan the QR code on the live facilitator scoreboard) - Enter your Bank Name (e.g.,
Bank Alpha) and Webhook URL (e.g.,https://brave-fox-42.loca.lt). - The backend will generate and return your assigned 8-character ASCII BIC code (e.g.
BANKXXXX).
(Alternative CLI option):
curl -X POST http://<FACILITATOR_IP>:9000/swift/register \
-H "Content-Type: application/json" \
-d '{
"name": "Bank Alpha",
"webhookUrl": "https://brave-fox-42.loca.lt"
}'Launch your bank application with your assigned 8-character BIC code:
PORT=8080 BIC=<YOUR_GENERATED_BIC> BANK_NAME="Bank Alpha" ./gradlew :bank-starter:runWatch your bank appear on the live projector scoreboard!
- Open
bank-starter/src/main/kotlin/org/craftedsw/bank/domain/. - Practice TDD to implement and refine:
- Internal deposits and balance calculations.
- Withdrawals and overdraw protections.
- Account statement printing in reverse chronological order.
- Statement filters (e.g., deposits only, withdrawals only, date ranges).
- Maintain 100% test coverage using JUnit 5 and AssertJ/MockK.
- Open
bank-starter/src/main/kotlin/org/craftedsw/bank/api/BankRoutes.kt. - Interact with and test your bank node using either IntelliJ HTTP Client (Recommended) or Traditional
curlCLI:
Open requests/bank-api.http directly inside IntelliJ IDEA:
- Select your target environment (e.g.
local-alphaorworkshop-live) in the top-right environment dropdown. - Click the green play button (
▶) next to any request to execute it with instant syntax highlighting and inline response assertions:# 2. Internal Customer Deposit: Credits 1,000.00 EUR to your account.# 3. Internal Customer Withdrawal: Debits 250.00 EUR from your account.# 4. Query Account Statement: Displays full statement table and running balance.# 6. Outgoing Cross-Bank Transfer: Dispatches a transfer through the SWIFT Hub to another team.# 7. Direct Incoming Webhook Simulation: Tests your node'sPOST /api/transfer-inwebhook handler.
- Deposit Funds (
POST /api/deposit):curl -X POST http://localhost:8080/api/deposit \ -H "Content-Type: application/json" \ -d '{"iban": "BE68BANKA0001234567", "amountCents": 100000}'
- Withdraw Funds (
POST /api/withdraw):curl -X POST http://localhost:8080/api/withdraw \ -H "Content-Type: application/json" \ -d '{"iban": "BE68BANKA0001234567", "amountCents": 30000}'
- Query Statement (
GET /api/statement/{iban}orGET /api/statement?iban=...):curl -s http://localhost:8080/api/statement/BE68BANKA0001234567
- Incoming SWIFT Transfer Webhook (
POST /api/transfer-in):curl -X POST http://localhost:8080/api/transfer-in \ -H "Content-Type: application/json" \ -d '{ "transactionId": "tx-1001", "fromIban": "BE68BANKB0001234567", "toIban": "BE68BANKA0009876543", "amountCents": 150000, "timestamp": 1727785800000, "reference": "Consulting Invoice" }'
- Outgoing Cross-Bank Transfer (
POST /api/transfer-out):curl -X POST http://localhost:8080/api/transfer-out \ -H "Content-Type: application/json" \ -d '{ "transactionId": "tx-1002", "fromIban": "BE68BANKA0009876543", "toIban": "BE68BANKB0001234567", "amountCents": 50000, "reference": "Split Lunch" }'
- The facilitator will ramp up simulated customer traffic!
- Your bank will receive continuous bursts of deposits, withdrawals, and cross-bank transfers.
- Goals for Round 3:
- Keep response times under 500ms for maximum points.
- Handle malformed payloads, zero/negative amounts, and non-existent accounts gracefully (
400 Bad Requestor422 Unprocessable Entity). - Maintain zero balance discrepancies.
- Only One Level of Indentation per Method: Use Kotlin expressions, functions, and standard library combinators (
map,filter). - Don't Use the
elseKeyword: Use guard clauses, early returns, and Kotlinwhenexpressions. - Wrap All Primitives and Strings: Use
@JvmInline value class Amount(val value: Int)orvalue class Iban(val value: String). - First-Class Collections: Wrap lists in dedicated domain aggregates (
Statement,AuditLedger). - One Dot per Line: Maintain Law of Demeter.
- Don't Abbreviate: Use full, intention-revealing names (
Transaction,StatementLine). - Keep All Entities Small: Maximum 50 lines per class/file.
- No Classes with More Than Two Instance Variables: Encourage high cohesion.
- No Getters / Setters / Properties for State Extraction: Tell, Don't Ask! Pass printers or visitors to objects rather than extracting internal state.