Banking Microservices
Five Spring Boot services connected by Kafka events
- Outcome
- A transfer returns after one debit and one event. Fraud checks, the OTP step, crediting and alerts run asynchronously over nine Kafka topics.
- My part
- Sole author. Designed the service split and event flow, and wrote all five services with their Docker, Kubernetes and CI files.
- Key decisions
- Debit first, compensate on failure
- Fraud checks off the request path
- Redis for OTPs and counters
- 5
- Spring Boot services
- 11
- REST endpoints
- 9
- Kafka topics
- 47
- Java files, about 2,000 lines
- 5
- CI workflows
- 19
- Kubernetes manifests
Problem
A money transfer has to move a balance, run fraud checks and alert the customer. Doing all of that in one synchronous request would tie those concerns together.
Solution
Five independently deployable Spring Boot services. A transfer debits the sender synchronously, saves the transaction as PROCESSING and publishes an event. Fraud checks, OTP verification, crediting the receiver and notifications then run asynchronously over Kafka.
Entry points
- ClientREST over HTTP
- RazorpayPayment webhook
REST services
- AccountPort 8081. Accounts, balances, credit and deduct. Consumes transaction.completed
- TransactionPort 8082. Transfers, OTP check, compensation
- PaymentPort 8085. Razorpay orders and webhook
Synchronous calls over OpenFeign: Transaction to Account (deduct, credit) and Fraud Detection to Account (balance).
Kafka
- Event backboneServices publish and consume these topics
transaction.initiatedfraud.check.cleanverification.requiredtransaction.otp.generatedtransaction.completedtransaction.refundedfraud.detectedpayment.completedpayment.failed
Event-driven services
- Fraud DetectionPort 8083. Velocity, unusual amount and share-of-balance checks
- NotificationPort 8084. Consumes six topics. Alerts are logged for now
Data
- MySQLJPA persistence for accounts, transactions and payments
- RedisOTPs with a 5-minute expiry, 60-second fraud counters
Trace a transfer
Step through what each service does for one transfer. This follows the code paths as written; it is not a live system.
- Client to TransactionPOST /api/v1/transaction with sender, receiver and amount.
- Java 21
- Spring Boot
- Kafka
- Redis
- MySQL
- OpenFeign
- Docker
- Kubernetes
- GitHub Actions
- Account service on GitHub (opens in a new tab)
- Transaction service on GitHub (opens in a new tab)
- Fraud detection service on GitHub (opens in a new tab)
- Notification service on GitHub (opens in a new tab)
- Payment service on GitHub (opens in a new tab)
Engineering decisions and details
Key engineering decisions
- Debit first, compensate on failure. The sender is debited synchronously before the fraud decision. If verification fails or the OTP expires, a compensating credit and a transaction.refunded event reverse it. It is a choreographed saga with no central coordinator.
- Fraud checks run off the request path. The transfer request returns after publishing transaction.initiated. Fraud Detection decides asynchronously: clean transfers complete, suspicious ones go through an OTP step.
- Thresholds live in configuration. Transactions per minute, the unusual-amount multiplier and the maximum share of balance are application.yaml properties, so rules change without code changes.
- Services read each other through APIs. Transaction and Fraud Detection get balances from Account through an OpenFeign client instead of reaching into its data.
Why these technologies
- Kafka
- Asynchronous communication, so fraud checks and notifications do not block a transfer
- Redis
- Temporary data: OTPs that expire by themselves and per-account counters with a time window
- MySQL
- Relational, transactional data accessed through JPA
- OpenFeign
- Declarative synchronous calls where a result is needed immediately
- Docker and Kubernetes
- Reproducible images per service, with Deployment and Service manifests for each
Implemented features
- Account creation with generated 12-digit account numbers, account status and daily limits
- Transfers with a status lifecycle that includes PROCESSING, PENDING_VERIFICATION, COMPLETED and FLAGGED
- Three fraud checks: velocity, unusual amount against a running average, and share of balance
- OTP verification for flagged transfers, stored in Redis with a 5-minute expiry
- Refund path that credits the sender back and publishes an event for notifications
- Per-service Dockerfile, Kubernetes manifests and a GitHub Actions workflow that builds and pushes the image
Engineering notes
- Money moves before the fraud decision, so the failure path matters as much as the happy path. That is why the compensation step exists.
- Redis does two short-lived jobs: OTPs expire on their own, and a counter with a 60-second expiry drives the velocity check.
- Services never share a codebase. They agree only on topic names and JSON payloads.
Not built yet
- API gateway and service-to-service authentication. The JWT library is a dependency of the account service but is not wired in.
- Idempotent consumers and an outbox for publishing events.
- Refund path not yet exercised end to end: the account service's credit endpoint is still unmapped, so the compensating call from Transaction needs finishing.
API reference
- POST
/registerCreate an account - GET
/{accountNumber}Account details - GET
/{accountNumber}/balanceCurrent balance - PUT
/{accountNumber}/blockBlock an account - PUT
/{accountNumber}/deductDeduct from the balance (called by Transaction)
- POST
/Start a transfer: sender, receiver, amount, description - GET
/{transactionId}Transaction status - GET
/account/{accountNumber}History for an account - POST
/{transactionId}/verifySubmit the OTP
