Migrating from Monolith to Microservices

08 Jul 2023 Konstantin Maevskiy

Starting a new software project or startup? Your initial decision could significantly impact your future success: choosing between monolithic and microservices architecture. Initially, a monolithic approach may be advantageous, providing simplicity in development, deployment, and testing, and enabling faster product launches. However, as your business expands, your software demands will grow. Shifting to a microservices architecture becomes a crucial, yet challenging, strategy. What obstacles might you encounter, and what factors should influence your choice? We’ll share insights from our experience with this transition.
A leading pharmaceutical client, after acquiring a startup we had initially partnered with, rapidly advanced their business goals by leveraging an existing solution. The collaboration yielded a successful medical app for monitoring asthma medication adherence, integrated with smart devices. Following their app’s success, the client aimed to amplify this achievement by introducing a customizable, white-label platform. Consequently, we evolved the app into a low-code platform, facilitating the easy creation of new applications, supported by smart packaging for partner branding. This required reconfiguring the original application into microservices, facilitating a new app development process primarily through the admin panel. This approach creates a foundation of functionality akin to assembling a product using building blocks, markedly reducing the workload for developers.
This ambitious vision necessitated a shift from the initial monolithic architecture to a microservices architecture. In our upcoming segments, we explore the best practices and strategies that facilitated this architectural transformation. Stay with us to discover the insights and approaches that made this transition successful.

Microservices vs. Monolithic Architecture

At the onset, let’s examine what monolithic and microservice architectures entail. A monolithic architecture represents a traditional software development approach where the application is constructed as a single, indivisible unit. This architecture tightly integrates all application components, from the user interface to data management, operating within one unified process. While this offers simplicity in deployment and management, it also leads to challenges in scaling and updating individual application segments. On the other hand, a microservices architecture advocates for dividing the application into small, independent modules communicating via lightweight protocols like HTTP or message brokers such as Apache Kafka and EventBus. Each microservice is responsible for a specific function and can be developed, deployed, and updated independently of others. This approach enhances flexibility, streamlines the update and scaling processes, but can increase the complexity of managing the overall system, especially when best practices in this domain are not followed.

The true utility of a microservices architecture shines when we consider specific scenarios, such as the integration and transition between different payment processing services. Let’s take an example where Stripe is integrated into our system as a dedicated payment component. In a microservices setup, should there be a need to switch to another payment system like PayPal, the architecture streamlines this transition. In a microservices framework, each service functions as an independent unit. This independence means that integrating a new service, like changing from Stripe to PayPal, involves updating or replacing just the specific payment microservice without needing to overhaul the entire application. This modular approach contrasts sharply with a monolithic architecture, where the tightly interwoven nature of components means that even small changes can require significant restructuring or reworking of the entire system. Moreover, in the context of microservices, the isolation of services also plays a key role in system resilience. For instance, if there are issues during or after the integration of a new payment service, these can be contained and resolved within the scope of that specific service. This isolated troubleshooting approach mitigates the risk of cascading failures that could impact the entire system, a common challenge in monolithic architectures. Thus, the microservices approach not only simplifies the integration of new services but also fortifies the system’s overall stability and reliability.

One of the microservices architecture’s strengths lies in the ability to use different languages and technologies for various services based on their purpose. For instance, Java might be employed for complex business logic processing, while Python could be used for data handling tasks. Thanks to service isolation and the ability to run multiple replicas of each microservice, deployment hardly impacts the system’s downtime. Each microservice resides in its own container, allowing for parallel operation and redundancy, which significantly reduces the risk of system unavailability during updates or maintenance. Updating a service only updates its container, minimizing downtime. Meanwhile, users can continue interacting with other system microservices. Microservices architecture also allows for precise system scaling. For example, during high-traffic events like Black Friday, the payment service can dynamically scale to handle the increased load. Similarly, if a partner launches a marketing campaign leading to a surge in authorizations, the authorization service can automatically scale to manage the influx.
Post-peak, the service replicas can be reduced. If the payment module fails for any reason, it firstly doesn’t result in a loss of cart items, since they are two separate, loosely connected services. Secondly, a new replica of the service is promptly created to replace the failed one. Thus, we achieve a highly flexible, failure-resistant, and modular system. We set up automatic scaling – both increasing and decreasing replicas based on the load. Creating a new application for a customer’s partner, we can not only assemble the app like a puzzle, using different parts of the system, but also manage these parts separately – updating, replacing, scaling, and so forth.

For further understanding of microservices architecture, refer to detailed articles by AWS and Azure, and a demonstrative video on the topic. Next, we will explore the best practices and principles that guided us successfully in transitioning from a monolithic application architecture to a microservices architecture.

How To Migrate From Monolithic To Microservices

As we navigated the journey from a monolithic application architecture to a microservices-based one, we discovered several key considerations and best practices that significantly eased the process and minimized risks. We strongly recommend against migrating the entire system to a microservices architecture in one fell swoop. Instead, our strategy emphasizes a gradual, step-by-step approach, beginning with an in-depth analysis of the current monolithic system. It’s crucial to understand all components of the system, their interdependencies, and to identify key business functions and processes. The primary objective at this stage is to group system components with similar functional roles into business domains, which will form the basis of future microservices. The illustration below showcases this approach using our original medical application, which we used as a foundation to build a low-code platform for developing new applications, identifying business functions and consolidating them into business domains.
The next step is to determine which parts of the system are the prime candidates for separation. This process demands a careful approach to maintain the system’s integrity throughout the transition. It’s important to start with areas where separation will bring the most benefit and the least risk to the business. We suggest beginning with functions that are loosely coupled with the core application and are easily isolated, such as authorization and background services. If a candidate also has a high load and specific scalability requirements, it moves higher up the priority list. For instance, a worker service that generates large reports on a schedule would be an ideal candidate. Following this principle, we continue to identify the next business domains in line for separation, transforming them into satellites and integrating them with the monolith via new connections, eventually leading to a constellation of separate, isolated microservices with weak links.

We recommend releasing one microservice at a time before moving on to the next. We rigorously test services with automated tests and monitor the traffic of inter-service interactions. If we observe significant traffic between certain individual services or between services and monolith components, we work to consolidate them into a single service. We aim to avoid a complex web of inter-service relationships that can overburden the system. This is an ongoing, iterative process of monitoring, reviewing, and making changes. The fundamental principle is that services should operate as independently as possible from the data of other services. This leads to the best practice that each microservice should have its separate data store. We recommend fragmenting the centralized database when transitioning from a monolithic architecture to microservices, though each case should be evaluated individually. By continuing the cycle of breaking down the monolith, analyzing the current picture of inter-service interactions, and reorganizing functional blocks, we gradually dissolve the monolith into a network of loosely connected mini-monoliths or microservices, effectively interacting with each other without strong interdependencies.

Key Factors in Successful Microservices Migration

Throughout the journey of transitioning from a monolithic architecture to microservices, several key factors are essential in ensuring success beyond the already mentioned aspects:

  • Formation of Product-Oriented Teams: Each team is dedicated to a specific service or functionality set, allowing for focused and specialized development efforts.
  • Cultivating a DevOps Culture: This involves fostering close collaboration between developers, operational engineers, and other stakeholders. The synergy created through this collaboration is instrumental in streamlining processes and enhancing overall efficiency.
  • Experience in Reactive and Event-Driven Design: Accumulating expertise in these design principles is crucial. It enables the creation of systems that can respond effectively to user actions and state changes, ensuring a dynamic and responsive user experience.
  • Implementing Continuous Integration and Continuous Delivery (CI/CD): These practices are vital for simplifying and accelerating deployment processes. By integrating CI/CD, teams can frequently and reliably release updates and improvements, significantly reducing time-to-market.
  • Rapid Deployment Strategies: Utilizing approaches like Blue-Green Deployment and Canary Releases minimizes downtime and risks associated with application updates. In Blue-Green Deployment, two identical environments are maintained – the active one (Blue) and another for the new version (Green). This method ensures seamless updates by switching the active environment post-testing. Canary Releases involve rolling out changes in phases, initially to a small user base, followed by a broader rollout post-verification. This phased approach is crucial for detecting and addressing potential issues early in the deployment cycle.
  • Advanced Monitoring with Request Tracking and Alert Systems: Implementing robust monitoring tools is critical for promptly detecting and resolving issues. This proactive approach to monitoring helps maintaining system stability and reliability.
  • Agile Methodology Application: Embracing Agile methodologies enables faster, more adaptive, and iterative development processes. This flexibility is key to addressing evolving business needs and market trends.

The final point in this list, the application of Agile methodology, plays a particularly significant role, which we will discuss in the following chapter.

Agile Migration: A Step-by-Step Approach

Agile Migration is our strategic approach tailored for seamlessly transitioning from a monolithic architecture to microservices, ensuring minimal disruption throughout the transformation. In this process, we meticulously address each service one by one, dividing the workload into manageable phases for more effective handling. This approach offers several significant advantages. First, it minimally impacts the live system, allowing us to maintain service continuity with ease. This aspect is crucial as it also simplifies the rollback process should the need arise. Another benefit of this strategy is the ability to work in parallel. It enables different teams to focus on separate components of the migration simultaneously, thus optimizing the use of time and resources. This parallel approach not only accelerates the migration process but also ensures that improvements are quickly visible, providing a clear overview of the progress. This visibility allows for swift iterations and enhancements, making the process highly efficient. The Agile Migration process is meticulously structured into three distinct phases:

1. Preparation: This initial phase involves a crucial step of converting projects to what is known as the ‘SDK style’. The SDK-style project format refers to a more modern and streamlined project file structure. It simplifies the project files, making them more readable and easier to manage, which is particularly beneficial for the migration process. Alongside this conversion, we also analyze dependencies using tools like the Portability Analyzer to assess compatibility and readiness for migration. This stage includes a thorough examination of project dependencies and an update of the build pipeline to effectively support the upcoming migration.

2. Execution: During this phase, we focus on standardizing our library projects to conform to the latest industry standards. This involves eliminating outdated manual processes and updating our testing frameworks. By aligning with the latest practices in automated testing, we ensure that our execution phase is both efficient and up-to-date with technological advancements.

3. Validation and Rollout: The final phase is critical for the overall success of the migration. We meticulously validate all changes in an integration environment to ensure that they perform as expected. Following this, we gradually roll out these changes in the production environment.

This agile-centric migration cycle revolves around a continuous loop of designing, deploying, developing, and testing, grounded in responsive and adaptable requirements management.

Thinking about moving to microservices?

If so, it’s crucial to rely on a team with a broad range of technology experience. Our expertise spans numerous areas, from developing mobile and web applications to cloud development and data management, enabling us to transition any complex monolithic architecture to microservices.

Let’s begin with a consultation to explore how we can assist you. Contact us today!

Let's arrange a consultation