Decomposing a Banking Application into Microservices for Scalability and Independent Development

Decomposing a Banking Application into Microservices for Scalability and Independent Development

Introduction

In the era of digital transformation, the banking industry faces unprecedented challenges. Customers demand increasingly innovative, fast, and reliable online services. At the same time, banks must optimize internal processes, manage resources efficiently, and comply with growing regulations. One effective strategy to address these demands is adopting a microservices architecture.

The Problem with Traditional Banking Systems

Traditionally, most banking applications are built on a monolithic architecture. This means that all functionality, databases, and business logic are bundled into a single large software package. Although this approach worked well for years, it no longer meets the demands of the modern banking environment:

- Making changes and adding new features requires modifying the entire application, which is very labor-intensive and complex.

- Scaling the system at the level of individual components is nearly impossible; it requires increasing the resources (CPU, memory) of the entire application, which is inefficient.

- Teams working on different functions (accounts, transactions, analytics) must work on the same codebase, often causing integration issues.

- Independently developing, testing, and deploying individual services is not possible, slowing down the innovation process.

In 2015, HSBC, one of Europe's largest banks, faced similar challenges. Their core banking system was outdated and could no longer meet modern requirements. They tried rewriting it from scratch, but the project stalled due to its complexity and scale.

Solving Problems by Decomposing into Microservices

HSBC found a way out of this impasse in the microservices architecture. Instead of a single monolith, they transitioned to a set of small, autonomous services, each responsible for a specific function. For example:

- AccountsService - responsible for opening accounts, closing them, checking balances

- CardsService - manages ordering bank cards, blocking them, changing PINs

- LoansService - handles issuing loans, monitoring them, and receiving payments

- PaymentsService - processes money transfers and settlements with other banks

- FraudDetectionService - monitors and suspends suspicious transactions

Each service is an independent component with its own data store, business logic, and API. They communicate with each other via REST, gRPC, or messaging (Kafka).

For example, when a user registers a new account from the online banking system, the AccountsService creates a new record in its database and also notifies the CardsService to issue a new bank card. During a payment, the PaymentsService verifies the existence of the account with the help of the AccountsService and passes the transaction details to the FraudDetectionService.

Transitioning to a microservices architecture allows the bank to:

- Quickly change individual components or add new ones without reconfiguring the entire system

- Scale individual services according to specific load

- Distribute work among different teams so that they do not affect each other's code

- Independently develop, test, and release new versions for each service separately

Identifying and Decomposing Services

But how do we split a monolithic application into microservices? HSBC used several principles:

1. By business capability - accounts, transactions, loans.

2. By subdomain - retail banking, business banking, investments.

3. By existing data structure - customer, transaction, product tables.

The most effective approach turned out to be domain-driven decomposition. Together with business domain experts, the key areas corresponding to the organization's business functions were identified:

- Customer Management: customer profiles, personal information

- Onboarding: new customer registration, KYC procedures, contracts

- Accounts: bank accounts, balances, transactions

- Cards: bank cards, transactions, security

- Loans: loan applications, payments, delinquencies

- Transactions: money transfers, conversion, sending/receiving

- Fraud Detection: detecting and preventing fraudulent and suspicious activities

- Reporting: reporting, analytics, business intelligence

Each of these areas became a separate microservice with its own backend and API. Some of them were further broken down into even smaller components when additional flexibility and scaling were needed.

Implementation Roadmap

How do we migrate from a monolith to microservices? HSBC chose an evolutionary approach - the "Strangler Pattern":

1. Add a proxy service to the existing monolithic application that redirects HTTP requests.

2. Extract individual functions into new microservices and direct HTTP requests to them via the proxy.

3. Gradually move more and more parts to the new architecture until the old monolith is finally decommissioned.

This approach allows us to gradually reap the benefits of microservices while minimizing risks and downtime.

At the same time, the infrastructure management model must also change:

- Instead of virtual machines (VMs), we should switch to containers (Docker)

- Use Terraform, CloudFormation, Kubernetes for infrastructure and deployment automation

- Use centralized systems such as Prometheus and the ELK stack for monitoring and logging

HSBC chose a microservices-oriented technology stack to build the new system:

- Java and Spring Boot framework for the backend

- ReactJS for frontend applications

- Netflix OSS (Eureka, Zuul, Hystrix) for service discovery and routing

- Kafka for messaging

- PostgreSQL and MongoDB for data storage

Their advice is to carefully select technologies for specific tasks and not "overload" the system unnecessarily to avoid losing the main advantage of microservices' simplicity.

Microservices Design Patterns

Now let's take a more detailed look at some patterns used when building a microservices architecture:

1. Database per Service

In traditional monolithic applications, data is stored in a centralized database used by all components. With microservices, each service has its own database, often of a different type (relational, document-oriented, graph).

For example, AccountsService may have PostgreSQL tables where information about accounts, balances, and transactions is stored. FraudDetectionService, on the other hand, uses Cassandra to quickly process hundreds of millions of transactions and detect anomalies.

This pattern increases the independence of each service, as changes to the data structure or scaling do not require modifying other services. At the same time, this approach requires careful data management to maintain data consistency and transaction integrity. Techniques such as Eventual Consistency, Sagas, and Event Sourcing are used for this.

2. API Gateway

When we have dozens of microservices, it becomes difficult for clients (mobile app, website) to communicate directly with each one. This is inefficient and violates security principles.

To solve this problem, an API Gateway is used - an intermediary service that sits between clients and microservices. It receives all incoming requests, authorizes them, validates them, transforms them, and then forwards them to the appropriate microservices. Responses return via the same route.

At HSBC, the function of the API Gateway is performed by the Netflix Zuul reverse proxy, which is integrated with the Oauth2 security server. This allows them to centrally manage access policies, API versioning, rate limiting, and metrics collection.

The API Gateway also allows creation of business-oriented interfaces (so-called Backends for Frontends), which facilitates client integration. For example, the /api/customer endpoint can combine data from CustomerService, AccountsService, and CardsService, which is directly served to the mobile application, hiding the details.

3. CQRS

Command Query Responsibility Segregation is an architectural pattern that separately considers reading (Queries) and modifying (Commands) system data. Traditionally, the database is used for both read and write operations, which complicates scaling.

With CQRS, Commands (Insert, Update, Delete) are processed in one service (or group of services), while Queries (Select) are executed separately by other services. This separation makes it possible to independently scale read and write services, optimize them, and choose technologies.

In a banking system, CQRS is useful, for example, for quickly calculating account balances. Let's say we have an Account aggregate that stores information about the account, its balance, and transaction history. Each new transaction changes this Aggregate (Command). At the same time, calculating the balance requires aggregating all relevant transactions (Query).

By implementing CQRS, we can separate these two processes. The Command service receives a new transaction, adds it to the Event Store, and updates the Account Aggregate. The Query service, on the other hand, will work with a separate database (Materialized View) optimized for quickly calculating balances. This View can be updated in near real-time based on new Events.


With this approach, the AccountCommandService is responsible for updating the account balance when adding a new transaction. It also generates and stores the corresponding Event. The separate AccountQueryService quickly returns the current balance from pre-aggregated data.

4. Saga Pattern

Different methods of coordinating transactions are used to maintain data consistency between microservices. Traditional two-phase commit (2PC) transactions work great in monolithic systems but are difficult to implement between isolated databases. The concept of Sagas was introduced to solve this problem.

A Saga represents a long-running transaction that consists of several steps, each performing a local ACID operation, but as a whole ensures eventual consistency.

Let's consider an example of issuing a new loan in a banking system:

1. LoanApplicationService creates a new loan application in the LoanApplication Aggregate and generates a LoanCreatedEvent.

2. RiskAssessmentService must assess the riskiness of the application. If the risk is acceptable, a LoanApprovedEvent is generated, otherwise a LoanRejectedEvent.

3. If the loan is approved, LoanContractService must create a new contract. In case of success, we should receive a ContractCreatedEvent.

4. Finally, FundsTransferService must deposit the loan amount into the client's bank account. After a successful transfer, we should receive a FundsDisbursedEvent.

An error can occur at any stage of these operations - incorrect risk assessment, a problem creating a contract, or insufficient funds. Saga's task is to correctly manage this process:

- For each step that is completed successfully, we should receive the corresponding Event and move on to the next stage.

- If an error is encountered at any stage, the steps already completed must be compensated for (rolled back).

- Compensation operations should cancel the results of the main flow. For example, if the risk assessment fails, the created LoanApplication should be deleted.

Technically, a Saga can be implemented in two ways:

1. Choreography - each service itself "listens" to the Events of the previous step and performs compensation itself in case of an error.

2. Orchestration - there is a central Saga Orchestrator service that manages the full business process, sends commands to all participants, and is responsible for rollback.

The HSBC banking system primarily uses the Orchestration style of Sagas, which allows process states to be stored centrally. At the same time, some auxiliary sub-processes, such as sending email notifications, are implemented in the Choreography style.


This is an example of a Saga Orchestrator using the NServiceBus framework. The LoanApplicationSaga class manages the entire flow of the loan process. It "reacts" to all relevant Events (`LoanCreatedEvent`, LoanApprovedEvent, etc.) and sends commands to various services accordingly (`AssessLoanRiskCommand`, CreateContractCommand, etc.).

If the process completes successfully at all stages, we finally call the MarkAsComplete() method, which completes the Saga. Otherwise, if an error is encountered somewhere (e.g., LoanRejectedEvent), we execute the compensation logic (in this case, we simply send a RejectLoanCommand).

The state of the Saga (`LoanApplicationSagaData`) is stored separately, which allows us to restore the process even in case of an emergency shutdown.

Conclusion

The microservices architecture is undoubtedly becoming the de-facto standard for building modern banking systems. It must meet the challenges of the digital age - provide flexibility, scalability, and independent development.

However, it is not a "silver bullet" and brings its own complexity in data management, transaction support, testing, and monitoring.

For successful implementation, it is essential to:

- Correctly identify microservices based on the business domain

- Use common patterns for interfaces (API Gateway), data access (Database per Service, CQRS), and transaction management (Sagas)

- Isolate security and resources through containerization (Docker)

- Automate testing, continuous integration, and delivery (CI/CD)

- Centralize logging, metrics, and tracing

The experience of HSBC Bank, as a global leader, shows that this path is laborious but necessary for the fundamental transformation of banking services. Their developers actively share knowledge through GitHub, conferences, and seminars, thereby contributing to the development of the industry.

Ultimately, the microservices architecture becomes not just a technical choice, but a strategy for large organizations to adapt and succeed in the digital world. It gives them the ability to quickly respond to growing consumer demands, be flexible and innovative in a competitive market.

I hope this article has helped you better understand the architecture of banking microservices, both from a business and technical perspective.

METTA SURENDHAR

MSc Integrated IT | Former Platform Engineering Intern @Invisibl Cloud | Blogger | Observability | Gen AI & RAG | SAAS'24 General Secretary | CTF'24 Tech-Ops Organizer | CEG 21-26

3 个月

Informative, David Shergilashvili! Hoping to implement this in my future hackathons.

回复

要查看或添加评论,请登录

David Shergilashvili的更多文章

社区洞察

其他会员也浏览了