This blog post is the third in a series detailing the modernization of a legacy payment platform. In the first phase, we focused on re-platforming the frontend to deliver immediate business value. By modernizing the user interface with React while maintaining the existing backend, the client was able to quickly enhance merchant-facing features and generate revenue without the delays of a full-scale backend rewrite.
The second phase tackled the critical infrastructure and deployment challenges needed to support future architectural improvements. By implementing DevOps practices and leveraging Kubernetes, we built a robust CI/CD pipeline, ensuring efficient and reliable deployments. These enhancements laid the groundwork for transitioning to a micro-frontend and microservice architecture.
In this third phase, we broke down the monolithic frontend into modular, independently deployable components and aligned the backend with a microservice architecture. Together, these approaches provided the flexibility to scale, integrate features seamlessly, and accelerate development cycles — key requirements for the client’s rapidly evolving needs.
In this post, we’ll explore how this transition was executed, the technologies involved, and how this approach positioned the platform for long-term success.
What micro-frontend architecture solves that monoliths can’t
As the client’s business expanded, the organizational landscape became increasingly complex. Multiple internal and external teams were developing tools and solutions independently, each tailored to address specific merchant needs. This decentralized approach presented a significant integration challenge: how to unify these diverse products into a cohesive system while preserving their independence and unique functionality. At the same time, the platform needed the flexibility to update and scale individual modules without risking disruptions to the entire system.
The micro-frontend architecture emerged as the optimal solution for these challenges. Unlike a monolithic frontend, which ties all components into a single codebase, micro-frontends allow independently developed modules to coexist within a unified interface. This modular approach enabled the client to integrate tools built by different teams, regardless of the technologies used, and rapidly deploy updates to individual modules. By separating concerns and reducing dependencies between teams, the micro-frontend architecture also facilitated faster development cycles and greater scalability.
Designing a modular micro-frontend architecture
We modernized the platform by implementing a micro-frontend architecture, focusing on achieving technical flexibility and creating a unified interface. To achieve this, we followed two key principles:
- Decoupling the monolithic frontend: We broke the tightly coupled monolithic frontend into smaller, independently deployable sub-applications. This allowed separate teams to develop, test, and deploy each sub-application without impacting others.
- Maintaining a cohesive user experience: We created a unified main application as the entry point, seamlessly integrating all sub-applications into a consistent interface. This approach ensured that merchants could interact with the platform as a single, cohesive product, even though its components were developed independently.
Centralized consistency with npm-managed shared libraries
To support this architecture, we introduced a shared package library managed via npm (Node Package Manager). This library centralized reusable UI components, styles, and utility functions, such as buttons, tables, and dropdown menus.
By consolidating these shared resources, we eliminated code duplication and ensured that any updates to shared elements were automatically reflected across all sub-applications. This approach provided a consistent user experience and simplified the maintenance of the codebase.
Dynamic module sharing with Webpack Module Federation and beyond
Our modernization journey leveraged Webpack’s Module Federation plugin to dynamically share modules between the main application and sub-applications.However, standard Module Federation primarily facilitates sharing individual components or utilities, such as buttons, tables, or dropdown menus. While effective for modularizing specific elements, this approach posed challenges for integrating full-scale applications with their navigation, routing, and state management intact.
To address this limitation, we extended the functionality of Module Federation by implementing and customizing a utility function called the Bridge. This enhancement allowed us to encapsulate entire applications, including their navigation and routing, into a single entry point, enabling seamless integration of sub-applications into the host system. Unlike the conventional approach, where each application’s pages and URLs must be individually shared, our solution consolidated these elements into a unified package. This innovation drastically reduced the overhead of managing multiple URLs and parameters across integrated applications.
Solving style isolation with Shadow DOM: To resolve style conflicts between sub-applications, we utilized the HTML Shadow DOM technology. Shadow DOM (Document Object Model) creates a self-contained, isolated “mini-DOM” within a web page’s main DOM. It allows developers to attach hidden DOM trees to elements, ensuring that the styles and components of one sub-application do not interfere with others. For instance, imagine two applications with conflicting button styles. Without isolation, the style from one application could override the other, creating inconsistencies. By encapsulating each sub-application within its own Shadow DOM, we prevented such conflicts while maintaining independent functionality. This solution also minimized development time compared to rewriting styles manually and ensured long-term maintainability.
Supporting future integration with diverse technologies: Our design also allows for future expansion to include applications developed with modern frameworks beyond React, such as Angular, Vue.js, or Svelte. This flexibility ensures that the platform can grow and adapt to the evolving technology landscape, accommodating a wide range of tools and team preferences while maintaining seamless integration.
Pushing the boundaries of existing standards: By combining Webpack Module Federation, the Bridge utility, and Shadow DOM, we overcame the limitations of standard modularization approaches to meet the complex demands of this project. This approach enabled us to go beyond existing standards, finding tailored solutions to integrate multiple independent applications into a unified and scalable system.
Decoupled architecture for flexibility and scalability
The overall architecture separates responsibilities between the main application and sub-applications. The main application manages core functionalities, such as authentication, navigation, and notifications, while sub-applications handle specific features like transaction management, reporting, and virtual terminal functionality. Sub-applications can be accessed either through the main application or directly via their own URLs, offering flexibility for both developers and users.
By combining centralized resources, dynamic module sharing, and a clear architectural separation, we delivered a highly modular, scalable, and future-proof platform. This approach aligned with the client’s organizational structure and technical goals, enabling faster development cycles, independent scaling of components, and seamless adaptability for future requirements.
Modernizing to microservices on the backend
The backend modernization began in Phase 1 with the introduction of the Proxy API. Acting as a middleware, it bridged the React frontend with the legacy backend by processing requests, reformatting data (e.g., XML to JSON), and creating modular endpoints. This Proxy API evolved into a network of intermediate services in Phase 3, which became the foundation for decoupling the monolithic backend into 25 independent microservices.
Each microservice was dedicated to a specific functionality, such as transaction management, reporting, or dispute resolution, and connected to its corresponding frontend sub-application via a dedicated API group. This modular design allowed teams to update, test, and deploy individual services independently, ensuring that enhancements to one service, like reporting, would not disrupt others. Additionally, the decoupled architecture maintained functionality even during individual service failures, preserving system reliability.
The resulting architecture separates responsibilities across the platform. Sub-applications interact with their respective intermediate services, which in turn communicate with the legacy backend. The architecture diagram (shown below) illustrates the layered design and interaction between sub-applications, intermediate services, and the legacy backend.
The accompanying diagram highlights how sub-applications, intermediate services, and the backend interact, showcasing the modular and resilient design achieved through this modernization effort.
Deploying microservices with Kubernetes and CI/CD
We utilized Kubernetes to orchestrate the distributed system, ensuring automatic scaling, fault tolerance, and high availability. This allowed us to efficiently manage workloads and seamlessly redistribute them during service failures, guaranteeing uninterrupted operation — an essential requirement for financial platforms.
The infrastructure and deployment architecture under Kubernetes were explored in greater detail in a dedicated blog post focusing on Phase 2.
Modernize your applications with professionals
If your organization is facing similar challenges, modernizing your application with a micro-frontend and microservice architecture can be a game-changer. However, these types of transformations demand strategic planning and advanced technical expertise to guarantee long-term success. That’s exactly what our team brings to the table.
Contact us to discuss how we can help drive your modernization journey and unlock the full potential of your application.