Skip to content

Sub-Project B: Core Banking System ​

[Done — Phase 1] — this page describes the system as implemented and covered by automated tests. Step-by-step log: Devlog — Phase 1.

1. Business context & goals ​

At the heart of every bank is the Core Banking Ledger — the source of truth for the balances of customer and internal accounts.

Design principles taken from real banking practice:

  1. Never store or UPDATE a balance directly: the balance is derived from double-entry ledger entries (SUM(CREDIT) - SUM(DEBIT)) — see ADR-006.
  2. Total Debit always equals total Credit for every transaction — enforced inside the Transaction aggregate root, see ADR-008.
  3. Immutable (append-only): posted entries are never edited or deleted. A transaction is cancelled by posting a reversal (Transaction.reverse()) — see ADR-002.

Goal: a Core Ledger Engine that keeps data consistent under many concurrent transactions on the same account — including when the application runs as several instances.


2. What has been built ​

ComponentRoleRelated decision
Account / AccountServiceCreate accounts, derive balance from the ledger, deposit() / withdraw()ADR-006
Transaction (aggregate root) + LedgerEntryDouble-entry posting, Debit = Credit invariant, symmetric reversalADR-002, ADR-008
Money value objectWraps BigDecimal + Currency, blocks currency mixing, fixed scale = 2ADR-012
Pessimistic lockingSELECT ... FOR UPDATE in withdraw(), prevents overdraft on concurrent withdrawalsADR-007
Outbox Pattern + KafkaPublishes transaction events to Audit/Compliance, Fraud Detection, Notification, Reporting without dual writesADR-009
Redis cache-asideCaches getBalance(), evicts after AFTER_COMMIT; withdraw() always reads the DBADR-010
PostgreSQL + Oracle2 Spring profiles, 2 Flyway migration sets, 45/45 tests pass on bothADR-011
REST API + KubernetesAccountController, 3 replicas, locking verified across real PodsADR-013

3. Verification results ​

  • 45/45 tests pass on both PostgreSQL and Oracle, with no profile-specific test changes.
  • Concurrent overdraft protection proven at two levels: a 2-thread test in one JVM, and 5 concurrent HTTP requests spread across 3 Kubernetes Pods (1 succeeds, 4 rejected with HTTP 409, final balance correct).
  • Integration tests against real infrastructure (Kafka and Redis via Testcontainers; Postgres/Oracle via docker-compose) — core components are not mocked.

4. Not yet implemented ​

  • Tracking who performed a change and a reversal workflow at the API level (currently only in the domain layer via Transaction.reverse()).
  • Asynchronous processing for high transaction volumes.
  • A distributed lock for OutboxEventPublisher when running several Pods (known limitation, see ADR-013).

Next: Architecture & Data Model · Tech Stack & Concurrency · Running & Testing · Lessons Learned

Simulated Banking & Fintech Systems — Java & Spring Boot