Migration from Monolith to Microservices Architecture

Sai Prasanth NG
Sai Prasanth NG
Partner October 28, 2019
#systemarchitecture

This whitepaper explains how a monolith architecture can be broken down into a microservices architecture.

Monolith Application

A monolithic application is a single-tiered software application in which the user interface and data access code are combined into a single program. ( from Wikipedia )

Microservices Application

Microservices - also known as the microservice architecture - is an architectural style that structures an application as a collection of services. ( from microservices.io )

Why Microservices?

As an application grows, we run into the scale problem. A monolith has two significant issues:

  • The boundaries between different domains are easy to violate.
  • A new developer will find it hard to understand the codebase and takes time to contribute.

Microservices solve both the problems because an application becomes a collection of services, and each service houses all the related logic. E.g., A payment service will accommodate all payments logic like the number of retries and payment gateway integration. The payment service would not house logic for sending out emails or storing product pricing details.

The Prep Work

Clearly define the domains

List down various domains of the system. For each domain, think about the responsibilities, the dependencies, and how they interact.

Each domain typically turns out to be one microservice. Every microservice can be reused for other applications/services.

Identify third-party dependencies

A failure in a 3rd party application should not affect our application. Add a microservice for each of the external services. E.g., Create a payment service to integrate with a 3rd party payment gateway.

This approach has some more advantages:

  • Creates a standard interface using which other services could process payments through different payment gateways.
  • Easy to switch payment service providers by making changes only to one service and not affecting other microservices.

Decide how services interact

We choose to use protobufs over JSON for communication between different microservices for following reasons

  • Protobufs are language-neutral and platform-neutral.
  • They have less overhead and are faster and simpler.
  • We could have bi-directional communication.
  • They provided versioning out of the box.  

Post prep work, the best way to go forward is to migrate logic from the monolith to the microservice in a step by step manner. With the step-by-step migration, we can continue to add features without disrupting the business.

Make sure browser tests are thorough

Ensure that the browser tests cover all the business use-cases of the application. These tests help us to automate the testing process during the integration of microservice with the monolith.

Integrating microservice with the monolith

In the prep work, we would have identified all the domains involved in the monolith. Pick a domain and start the development of the microservice. Focus unit test cases.

There are three different types of microservices - zero data, read-heavy, and write-heavy. Each of them requires a unique migration strategy.

Zero data microservices

This type of microservices doesn’t have any data of their own and have only logic related to integration. They are easy to migrate and are generally clearly defined as lib files in the monolith. So we have come up with the following steps to migrate these kinds of microservices.

  • Migrate logic present in the lib files in the monolith to the microservice.
  • Define a new client class that integrates with the microservice and pass the object of this class instead of the object of the lib class files.

Read-heavy microservices

These microservices have data and are read-heavy. Such services are generally integration services, but they cache the data from other applications, usually to improve the performance. They typically don’t have many use-cases that modify this data. To modify data, they call the other application APIs. Following are the steps to migrate these kinds of microservices:

  • Phase 1:
  • Migrate only the logic related to “read” to the microservice.
  • Use the Adapter design pattern to redirect all the reads to the microservice.
  • Modify the controllers to call this adapter object rather than the model object for the related data.
  • Do a one time dump of related data from the monolith or run a one time sync from the 3rd party application, whichever is cheaper.
  • Release to production. This step usually involves downtime for the application as you want to be sure that the data is not modified while taking the database dump.
  • Phase 2
  • Migrate logic related to write from the monolith to microservice.
  • Modify the adapter class to call the microservice for all the create/update and delete operations.
  • Release to production.  

Write heavy microservices

These microservice generally are not integration services. They have their own data and are responsible for any modifications. Two different approaches can be used to migrate these kinds of microservices.

Approach 1

We can do a big bang release; we can build the microservice, work on the integration with the monolith, do a one time dump of the required data from the monolith to the microservice and release it to production. Doing so generally requires downtime to ensure that no modifications happen when we are migrating data to the microservice.

Approach 2

Another approach is a more agile way of migration:

  • Phase 1:
  • Migrate only the logic related to “read” to the microservice.
  • Use the Adapter design pattern to redirect all the reads to the microservice.
  • Add additional hooks/sync logic to propagate newly written changes from the monolith to the microservice.
  • Modify the controllers to call this adapter object rather than the model object for the related data.
  • Do a one time dump of related data from the monolith or run a one time sync from the 3rd party application, whichever is cheaper.
  • Release to production. This step usually involves downtime for the application as you want to be sure that the data is not modified while taking the database dump.
  • Phase 2
  • Migrate logic related to write from the monolith to microservice.
  • Modify the adapter class to call the microservice for all the create/update and delete operations.
  • Remove logic related to syncing the write data from monolith to the microservices.
  • Release to production.

Few points to keep in mind

  • Run the browser tests to ensure that there were no bugs.
  • Post launching a microservice to production, remove the redundant logic/data from the monolith.
  • Keep a sprint gap between each phase of migration to find and fix bugs.
  • It is tough to rollback microservices that are write-heavy. Keep two sprints gap between migrating different microservices to ensure that the application is stable. You can spend time planning the migration steps during these two sprints.