Steps to migrate from Monolith to Microservices
By: Mohamed El Habib
Tags: Monolith, Migration, Cdc, Microservices

In this article we will describe steps to migrate a Monolith application to a Microservices based platform.

Definitions

First of all let’s define what is the means of Monolith application and what is the means of Microservice.

Monolithic Application

A monolithic application is an application built with a single technology stack (java, .NET, node.js, etc.) that implements several large features, for example all the features of an e-commerce platform. This kind of application needs a large team to manage the application’s large scope. The components of this kind of application are strongly coupled and every release is an event that requires significant roadmap synchronisation efforts due to important regression risks.

One characteristic of a monolithic application is to have only one big database with several integrity relations between entities of different components. This makes the horizontal scaling complex and expensive, the probability of concurrent update and locking is very high.

Microservices Application

A microservice application implements a human size scope that can be managed by a small development team. The communication between a microservice and other applications is done using a contract (Rest API contract, Messaging format Contract), this contract can have several versions.

This makes it easy to replace a microservice with another implementation, possibly based on another technology. The only condition is to respect the service’s contract.

The reduced scope and the contract-based dependency management make it easy to frequently deploy this kind of application with controlled risk.

Because this kind of application typically requires few resources, horizontal scalability is easy to achieve and more efficient (we scale up only components that need to be scaled and not all the system).

About the context

After having some definition let’s describe a little bit the context, because there is no technical solution appropriated to all contexts. So let’s say having a client that have an existing e-commerce platform. This platform include one or more websites, one or more mobile application, some internal application ( back office and mobile application) used to manage the system and process orders.

This client need to “Define the IT architecture and operating model for the next 10 years”

This request comes with the following constraints.

- No Big Bang: the construction of this kind of platform can take more than a year and waiting several years to see the result is a big business risk. To mitigate this risk, we have to take an iterative approach: split the system into smaller components and migrate them one by one.

- Test and learn: when building a new system we make some hypotheses. As we develop the system, the only way to confirm them is to go to production. The idea with the “Test and learn” approach is to go to production frequently to get feedback quickly and adapt or fix our hypotheses.

- Without parallel running: having two systems running in parallel and making each modification twice, in the legacy system and in the new system, is an unacceptable extra cost, modification have to be done only once and all the system have to be up to date.

Our solution

To respond to the client request we decide to build a microservices based platform where each microservice will manage one functional scope. This architecture responds to the need for scalability of the system: if one version of a microservice needs to be completely redesigned or rebuilt, it can be done easily because the only constraint is to respect the service contract.

Deployment will progress along two axes.

The first one is the population of users who will see a new version of one feature. This will be an implementation of the Canary Release pattern, which makes it possible to test hypotheses on a small population and, when we are happy with the result, we can increase the population progressively until we cover the full population. If the first tests are not conclusive we can easily update our hypotheses. For each hypothesis we define key performance indicators (KPIs) that are monitored continuously and form the basis for our decision to continue or stop.

The second axis is the migration of features from the legacy system to the new one. This gives visibility to the progression of the migration. We will use the strangler pattern to achieve that.

With microservices we are able to develop a new version of one small functional scope. To deploy this version into production and integrate it into the legacy system as soon as it is available we use the strangler pattern. To manage risk and apply the test and learn approach, we use the canary release pattern.

The migration approach

Our migration approach will be operated in four steps

1. Taking control

2. Taking over user experience

3. Taking over business logic

4. Taking over persistence

Taking control

First of all, we need to hijack the complete experience provided by the monolith and replace it with our new microservice-based experience. This can be achieved by injecting a new component that diverts incoming traffic to the most appropriate experience. We have called this new component Strangler Router. This component must be configurable via the Canary Release component.

Taking over user experience

The second step is to implement a first version of new UI microservice to update the UI of one specific functional scope(1). This new UI uses an existing API or a Glue Code from the legacy application(2). The Strangler Router component forwards only the relevant URI to the new UI(3). Thanks to the Canary Release component only a limited number of users can see this new version(4). This gives us the opportunity to quickly update the user experience and validate hypotheses with minimal cost and delay.

Taking over business logic

The third step is a second version of the UI microservice that updates again and again the user experience(1). This version interacts with a new microservice that adds new functional features(2) and still relies on existing API or a Glue Code from the legacy application for existing features and persistence(3). Strangler Router component still forwarding only the concerned uri to the new UI. Based on the Canary Release component only limited clients can see this new version.

Taking over persistence

The fourth step is a third version of the UI microservice that updates again and again the user experience(1). This version continues to interact with an API microservice that adds new functional features but we stop relying based on existing API or a Glue Code from the legacy application(2).

This version of the API microservice has its own persistence layer and starts to create its own data for the functional scope that implement(3).

As Strangler Router and Canary Release components are still working as described above, we still have users forwarded to legacy systems. Due to the strong coupling between legacy system entities we still need to synchronize data with the legacy system(4).

There are several solutions to implement the data synchronization component, below we list some and choice have to be according to the business and technical constraints.

The first solution is periodic export to file from one side and periodic import into the other side, this solution can be used if only one side modifier the data and that real time is not necessary.

The second solution is to use API calls to make synchronization, we have to manage retry for the case where the API is down.

The third solution is to use event based solutions, we add code into legacy and new systems that publish an event into topic or queue and another code that consumes published events. The challenge with this solution is to be able to update the large base code of legacy system without forgotten cases.

The fourth solution is to use the CDC, change data capture, capability of the legacy database. In fact in databases, CDC is a set of software design patterns used to determine (and track) the data that has changed so that action can be taken using the changed data. CDC is also an approach to data integration that is based on the identification, capture and delivery of the changes made to enterprise data sources.

Database store into special tables the history of change, the original version and the new version. CDC can be coupled to some message-oriented middleware like Kafka (data streaming) to allow event broadcasting in the microservice ecosystem. This solution enables near real time data but due to the low level of broadcasted event there are some complexities to transform technical events to business events. You can get more details into Two way data sync between legacy and your brand new micro-service architecture or https://www.youtube.com/watch?v=ur0Z9uOKul8.

Conclusion

The four steps described above are applied to only one functional scope. To migrate all the functional scope of the legacy system, this approach must be applied to each functional scope that has to be migrated.

The above schema illustrates the state of the system (new and legacy) into the middle of the migration. We see that the Strangler Router component still forwarding only the concerned uri to four UI microservices. Based on the Canary Release component we control the percentage of clients who can see those microservices.

Canary Release components have different percentages for each microservice, this shows the difference in the progression of the migration between functional scopes.

We can see that the fourth microservice has a database without synchronization components, this can be for new data created by the new system or for data no longer managed by the legacy system.

We have several synchronisation solutions at the same time (CDC for one and API call for the other in this example).

This approach is designed to be customer centric, try to go to the production quickly and start updating the user experience. At the end the complete shutdown of the legacy system can take several time, the same for the big bang or more, this depend on the priority we give to the switch from legacy to new system versus developing new features.