This document explains the backend architecture, main technologies, important design patterns, and the core technical tradeoffs behind the current banking prototype.
The backend currently includes:
Customer ServiceAccount ServiceDeposit ServiceAudit ServiceGateway- shared building blocks
.NET 10ASP.NET Core Web APIEntity Framework CorePostgreSQLRabbitMQxUnitFluentAssertionsWebApplicationFactoryDocker Compose
- customer master data
- customer status lifecycle
- account lifecycle
- balance ownership
- account activity history
- deposit intake
- idempotency handling
- transaction state management
- pending-review and compensation flow
- audit persistence
- audit retrieval
The backend is split by responsibility, not by technical layer.
Why:
- clearer ownership
- fewer cross-domain side effects
- better path to scale and independent evolution
Deposits are long-running workflows that touch multiple services.
The current design uses saga-style coordination for:
- initial deposit request
- account posting
- audit write
- compensation or review when something fails
Why not distributed transactions:
- too tightly coupled
- harder to operate
- less resilient in cloud-style environments
Deposit Service persists transaction state and outbound integration messages before dispatch.
Why:
- reduces the risk of “saved in database but event not published”
- improves recovery and retry behavior
Critical write paths use idempotency to prevent duplicate financial effects.
This is especially important for:
- deposit submission
- compensation handling
- posting references
The backend is shaped for operator workflows, not only CRUD storage.
Examples:
- account activity history
- pending-review queries
- retry and manual resolution operations
The account service is the source of truth for balance updates.
Tradeoff:
- more service-to-service calls
- but much clearer balance ownership and consistency boundaries
Audit is intentionally separated from the main transaction services.
Tradeoff:
- more moving parts
- but better compliance isolation and clearer retry behavior
Each service uses local persistence guarantees, while cross-service consistency is eventual.
Tradeoff:
- more status handling and operational complexity
- but better scalability and fault isolation
The system keeps internal ids for machine boundaries, while exposing:
customerNumberaccountNumbertransactionNumber- operator
referenceNumber
Tradeoff:
- more identifiers to maintain
- but much better operational usability
The backend uses layered testing:
- unit tests for service logic
- integration tests for HTTP and persistence behavior
- contract-oriented checks for API expectations
This keeps changes safer in areas such as:
- balance mutation
- deposit status transitions
- compensation handling
- account-number lookups
- no centralized query/read-model service yet
- no full production-grade migration system yet
- authorization is still coarse compared with real banking roles
- observability is good for local development, but not yet complete for production operations
- add richer read-model aggregation for customer-level activity
- strengthen role-based authorization
- formalize migrations and data versioning strategy
- expand resilience and monitoring around RabbitMQ and retry paths