Distributed banking system ("Mini-Bank") using microservices architecture. The project focuses on scalability, high availability (HA), and eventual consistency - using modern modern Java stack and tools.
- Java 24 + Spring Boot 3.5.7
- Spring Cloud Gateway + Spring Cloud LoadBalancer
- Apache Kafka (Confluent) + Zookeeper
- PostgreSQL (Bitnami images) with async streaming replication
- Hibernate + JPA, optimistic locking
- Deployment: Docker Compose
- Logical sharding of accounts by
account_id % shard_count(default = 2) - Each shard has one master (R/W) and one asynchronous physical replica (read-only)
- Transaction service uses separate unsharded PostgreSQL cluster
- Local ACID per shard via optimistic locking (
@Version) - Cross-shard eventual consistency via Saga orchestration pattern
- Idempotency guaranteed by
processed_eventstable
- Orchestrator: Transaction Service manages the Saga pattern to ensure eventual consistency across microservices.
- Coordination: Kafka is used as the message broker for asynchronous communication.
- Apache Kafka is used for all Saga commands and compensation events
- Exactly-once processing via
processed_eventstable
- Account Service (sharded)
Responsibilities:
- Account creation, balance inquiry, deposit/withdraw
- Local transfer operations (DEBIT/CREDIT) within Saga
- Operation history per account
Key Entities:
Account(id: Long, balance: BigDecimal, version: Long)
AccountOperation(
accountId, type ∈ {DEPOSIT, WITHDRAW, DEBIT, CREDIT, COMPENSATION_*},
amount, balanceAfter, transactionId, operationTime)REST API (via Gateway):
POST /api/accounts → create account (id generated in gateway)
GET /api/accounts/{id} → get account
POST /api/accounts/{id}/deposit → deposit
POST /api/accounts/{id}/withdraw → withdraw
GET /api/accounts/{id}/operations → local operation history
- Transaction Service (Saga Orchestrator)
Responsibilities:
- Initiates money transfer between any accounts (possibly cross-shard)
- Persists transaction state and coordinates Saga steps
- Provides transaction history
Key Entity:
Transaction(
id: UUID,
fromAccountId, toAccountId,
amount,
status ∈ {PENDING, COMPLETED, FAILED, COMPENSATING, COMPENSATED},
sagaStep, retryCount, nextRetryAt)
TransactionEvent(type: String, transactionId: Long, accountId: Long, amount: BigDecimal)REST API (via Gateway):
POST /api/transactions/transfer → initiate transfer
GET /api/transactions/history/{accountId} → transactions involving account
- API Gateway
- Single entry point (
http://localhost:8080) - Generates account IDs (in-memory AtomicLong)
- Routes
/api/accounts/**→ correct shard via custom GlobalFilter - Routes
/api/transactions/**→ Transaction Service - Balances load (round-robin) across service replicas
- Aggregates history at
/api/history/{accountId}(account operations + transactions)
Public Aggregated Endpoint:
GET /api/history/{accountId} → returns chronologically sorted list of:
- Direct deposits/withdrawals
- Incoming/outgoing transfers (from Transaction Service)
High-level view of system interactions.

Physical layout of containers and replication.

Behavior and flow of operations (Saga execution).

- No automatic PostgreSQL master failover (manual
pg_promoteonly) - Static service discovery (no Eureka/Consul)
- Single Kafka broker (development profile)