Modernize Your Payment System with Microservices: Part 5 – Utilizing Frameworks to Enhance Efficiencies and Minimize Dependencies

May 09, 2024 | Hui Wang

In a fast and ever-evolving digital landscape there is a relentless pressure on IT systems to be adaptable and nimble. Unless you’re presented with an opportunity to build a green field project from scratch, you will typically be working with existing systems, which are often legacy systems.  The definition of legacy is highly debatable, but we can all agree legacy systems were once cutting edge but have been surpassed by newer technologies. There is really nothing wrong with legacy systems as they have stood the test of time and have persisted due to their historical significance and functional necessity.  However, legacy systems will begin to crack under the pressure of change and hinder business growth. Ultimately the whispers of modernization will grow louder until they can no longer be ignored.

In our on-going blog series about legacy migration, we already covered topics on why microservices are advantageous and how to decompose into small microservice domains.  We have also touched upon the enhanced resilience and the security posture a modern microservice solution provides. In this blog, we will focus on the next aspect: the benefits of a modernization framework and how to use one effectively.

What is a Modernization Framework?

One does not simply create a set of microservices and deploy them to the cloud and call themselves modernized. A modernization framework is a set of principles, patterns, and practices that holistically guide the design and implementation of modern architectures, based on microservices.

If we just focus on the key components of a microservice architecture, then we typically see the following practices emerge: 

  • Cloud-Native Infrastructure: Microservices are designed with a cloud-first mindset and deployed on cloud-native infrastructure to harness the full potential of cloud platform offerings. This includes technologies for running containers (e.g., Azure Containers, Amazon ECS) or serverless functions (e.g., Azure Functions, AWS Lambda).  The managed infrastructure provides isolation, scalability, and portability for microservices.
  • Service Discovery: Enables microservices to locate and communicate with each other dynamically.  Service mesh technologies, such as Consul or Istio, maintain a registry of available services and their locations. Microservices query the registry to discover other services they need to interact with.
  • Service communication: Communication and coordination between the microservices are achieved using web API calls synchronously or event driven asynchronously. Both synchronous and asynchronous communication patterns decouple services from one-another.
  • API Gateway: Client requests are sent to the API gateway, which forwards them to the appropriate services on the backend. It decouples clients from services, supports versioning, and handles cross-cutting concerns like authentication and load balancing.

Without defining any further guidelines, a microservice system can quickly become quite complex in the midst of development, leading to inconsistencies and additional overhead. The following principles and practices helps to address some of the common challenges:

  • Data Domains: Each microservice should clearly focus on a specific domain, typically representing areas of knowledge or activity within a business or organization. Isolating data domains ensures better maintainability and scalability.
  • Service configuration: The configuration and management of each microservice settings and parameters should be externalized such as Spring Cloud Config, Azure KeyVault, or Kubernetes ConfigMaps and Secrets.
  • Continuous Integration/Continuous Deployment (CI/CD) Pipeline: A robust CI/CD pipeline automates the build, testing, and deployment of microservices. It ensures rapid and reliable delivery of changes to production. Furthermore, infrastructure should be defined as code and changes managed via a deployment pipeline.
  • Real-Time Monitoring: Effective monitoring is essential for microservices. Real-time monitoring tools track performance, resource utilization, errors, and other metrics. Alerts and dashboards help detect issues promptly and ensure optimal system health.

By setting a clear and consistent guidance for the design and implementation, these principles work together to create a flexible, scalable, and resilient microservices architecture. Each piece contributes to the overall success of the system, allowing organizations to build and maintain modern applications efficiently.

How to Use a Modernization Framework Effectively?

Having an effective modernization framework plays a pivotal role in transforming legacy systems into agile, adaptable solutions. It helps guide organizations through the intricate process of updating their software infrastructure while preserving critical functionality. Two distinct paradigms—Domain-Driven Design (DDD) and Event-Driven Architecture (EDA)— have stood out for me as powerful tools in some recent projects of mine.  We touched briefly on these in a previous article, but let’s explore how these paradigms can be effectively used to ensure software systems not only keep pace with technological advancements but also align closely with business domain nuances.

Domain-Driven Design

Domain-Driven Design (DDD) is a software development approach that focuses on the business domain and its logic, rather than the technical details and implementation. DDD helps to identify and define the boundaries and interfaces of each microservice, based on the business capabilities, domains, and subdomains of the system.  Here are three DDD concepts that we have used effectively in projects to ensure microservices have distinct boundaries and loosely coupled to minimize knowledge about each other: 

  • Bounded contexts: Begin by defining distinct boundaries for your microservices. Each boundary represents a specific business domain or functionality. Once the boundaries are established, create individual microservices for each context and within each context, maintain a consistent vocabulary. This shared language ensures clear communication between domain experts and developers. In a recent project, we identified ‘account funding’ as a distinct business domain. We designed a dedicated service with separate databases to prevent tight coupling. This service also housed all the necessary business validation rules specific to account funding.  As a result, we created a service with a specific responsibility and avoided any cross functional dependencies. 
  • Aggregates: Aggregate related domain objects which needs to be treated as a single unit and nominate a single aggregate root to be the consistency boundary. In practice, we identify the entity that best serves as the guardian of the aggregate based on business domain’s use cases. For example, in a solution containing accounts and users, we elevate the account entity as an aggregate root. Domain logic and rule enforcements are placed within the account aggregate root since we determined you could not have an account without a corresponding user.  This ensured the aggregate remained in a valid and consistent state. 
  • Event Sourcing: Instead of storing the current state of an entity, event sourcing stores a sequence of events representing changes to the entity’s state over time.  In practice, we identify entities that should be managed by event sourcing since not all entities needed this level of historical tracking.  Each event represented a meaningful fact, typically a state change and stored in an append-only manner.  For example, in an order purchase flow, we stored multiple money movement events to represent the amount of money withdrawn and deposited into source and target accounts. These domain events serve as an audit trail of everything that has happened and can also be used to replay events.

Event-Driven Architectures

Event-Driven Architectures (EDA) are architectures that use events to trigger and coordinate the actions of the microservices. Events are messages that represent something that has happened in the system, such as a payment request, a payment confirmation, or a payment notification. EDA help to enable and manage the asynchronous communication between the microservices, using messaging queues, brokers, or buses, such as Kafka, RabbitMQ, or Azure Service Bus.  The following are three EDA concepts we have leveraged during modernization projects to create a reactive solution that work seamlessly together and handles failures gracefully to ensure resilience: 

  • Event streams:  Event streams are fundamental to event-driven systems. They allow components to react to events as they happen. Each event corresponds to an important action or specific occurrence or state change. Events occur in a specific sequence, allowing systems to process them in the correct order. Once an event is generated, it cannot be changed. This immutability ensures an accurate historical record.  In practice, we have used event streams to publish simple events like ‘user onboarded’ to more complex orchestrations like transferring funds between user accounts.  This helps us design a system that reacted to changes and enabling timely actions. 
  • Event brokers: An event broker acts as a mediator between event publishers (producers) and event subscribers (consumers).  It facilitates the seamless transmission of events between different components within a system.  While working on a project deployed on Azure, we leveraged the Azure Event Grid as a fully managed pub-sub message distribution service.  We think of it as a central hub that efficiently routes events, ensuring they reach the right destinations.  It can be configured to route events based on predefined rules and guarantees at least once delivery.  For high value or critical events, we can route using Event Grid filters and queue the events in the Azure Servicebus to ensure guaranteed in order processing. 
  • Saga Pattern: To address a common event driven asynchronous microservice design challenge, the Saga pattern should be used. Sagas are sequences of local transactions that maintain data consistency across services. When a business operation is long running and spans multiple services, sagas ensure that each step is either fully completed or fully compensated if an error occurs.  In practice, on a project that was fully deployed on Azure, we leveraged Azure Durable functions, an extension of Azure functions, to implement the saga pattern. There is an orchestrator function that manages a sequence of local transactions from saga participant. Each participant executes an activity and communicates with the orchestrator to ensure system consistency. If an activity fails or needs to be rolled back, the orchestrator triggers compensating actions.

These modernization framework paradigms—DDD and EDA—empower us to build resilient, adaptable systems that align closely with business needs. By dissecting complex domains, embracing event-driven communication, and leveraging microservices, recent projects have successfully navigated the modernization journey.

Closing Thoughts

Legacy systems continue to persist due to various reasons, even if they are no longer cutting-edge. They serve as a bridge between the past and the present, carrying forward valuable functionality and historical context.  They do require much more careful attention, inherent in maintaining aging technology.  To modernize a legacy system is no small endeavour; meticulous planning, strategic execution, and leveraging a set of principles, patterns, and practices defined by a modernization framework can transform outdated systems into agile, adaptable assets that drive growth and innovation in today’s dynamic market landscape.  A microservices architecture only provides a foundation for building scalable, maintainable systems while allowing room for expansion and customization. But being aware of thoughtful design, adherence to principles, and the collaborative effort with business contributes to the overall success of the system, allowing organizations to build and maintain modern applications efficiently. In the next blog we’ll cover how effectively migrating to microservices can reduce your development and operational costs. Thank you for reading and we invite you to follow Level19 for more great content!

Previous Blogs in the Legacy Migration Series:

Download PDF