This article is part of the blog series “Fearless Monolith to Microservices Migration” written by Johannes Bräuer and Jürgen Etzlstorfer. This section will be updated with links to all other articles as soon as they are available.
Part 1: Introduction
Today’s requirements on modern web applications demand high availability and scalability for which a microservice architecture has shown to be a promising solution. However, building a microservice architecture from scratch is sometimes not possible when monolithic web applications are already in place and cannot be replaced by simply flipping the switch. Instead, it is required to break up this monolith step-by-step into microservices that represent self-contained components, have their own data management, and are developed, deployed and maintained ideally by a single team. Consequently, these single services can up – or downscale on demand and can evolve depending on changing requirements. Another advantage for decomposing the monolith into microservices is to move those new components to cloud environments to achieve the full advantage regarding availability and scalability. In theory, this task of breaking up a monolithic 3-tier application into its microservices sounds easy, but in fact it is a challenge that various organizations are facing right now.
For instance, there is uncertainty about:
- how to run the monolith in parallel while developing the microservices,
- the dependency structure within and outside the monolith,
- how a single request walks through the code base,
- possible candidates that could represent a microservice, and
- how the monolith would live without them.
To that end, we want to walk you through the different stages of identifying and extracting a microservice, as well as strangling it around its origin – the monolith. For this purpose, in the upcoming blog series we’ll provide step-by-step instructions showing the best practices we have identified for migrating a monolith to the cloud. In fact, we will adopt the procedure for breaking a monolith proposed by Andreas Grabner on an open-source project from our partner Red Hat. We will further show you how Dynatrace helps you in identifying those parts of your application that might be perfect candidates for extracting them as microservices and how to ensure performance and availability to fearlessly migrate your application to the cloud.
Whether you have your monolithic legacy application, or you have built a prototype in a monolithic manner to evaluate market acceptance of a novel idea, there comes eventually a point where you must decide for a scalable and robust architecture in terms of microservices. Furthermore, to fully exploit the advantages of cloud environments such as auto-scaling and cost-efficiency, a cloud-native application architecture is a prerequisite (as shown in the Figure below). Also, in terms of a fast go-to-market and short deployment cycles, monoliths have severe disadvantages compared to microservices.
Briefly summarized, the three main advantages of a microservice vs. a monolithic architecture are as follows:
- Microservices scale sufficiently: Cloud platforms – where we finally want to run our microservices – can horizontally scale up or down in seconds and in an auto-scaling manner. This is performed without bothering the application developer about the underlying infrastructure.
- Microservices can be deployed and tested independently: A well-designed microservice architecture provides the freedom to the developers to deploy and test a new service release without impacting other teams (services).
- Microservices architectures fit with agile development environments: Breaking up a monolith into microservices often goes along with breaking up a silo-oriented team structure into self-organized and autonomous groups. This brings us back to Conway’s Law, which states that: “any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure”.
Our Monolith and first Ideas how to break it
TicketMonster in its monolithic appearance, contains all components that are representing the presentation layer and business logic in one deployable artifact. Besides, the monolith hits one database that stores the entire data of the application. Based on this picture, you may imagine that the application in this form is difficult to maintain since any change causes a redeployment of the entire monolith; an issue we will get rid of throughout the blog post series.
From a conceptual point of view, the following figure shows some issues of a monolithic application and how to extract a first microservice. Although this is a high-level overview of the process, it serves as a starting point and helps us to understand the needed steps in this journey.
A fearless monolith-to-microservice migration
To fearlessly break up the monolithic TicketMonster application without losing control over the changes, we will show you how Dynatrace supports you in this journey and we will investigate and apply different techniques and best practices. A few of them we spoil at this point:
- Canary release: When following our ideas on how to break up the monolith, sooner or later you will have to extract the UI. In a real-world scenario, it is not possible to simple route the entire traffic through this new front-end since failures would affect everybody. As a result, we perform a canary release meaning that it will be available as its own release next to the legacy UI, but just facing a selected user group. If the UI becomes stable, the user group will be increased until all users are working with it.
- Custom service detection: Dynatrace can help you in identifying candidates for microservices. Therefore, we can employ the feature of Custom service detection that allows you to virtually define service entry points, and for our purposes we leverage it to decompose a monolith without touching a single line in the code base. Read more on this in Andreas’ blog: Breaking the Monolith: An 8 Step Recipe.
- Strangler pattern: For breaking up critical systems, Martin Fowler proposes to merge a new microservice with the monolith and do not let it live by its own. The most important reason to apply this pattern is the reduction of risk by monitoring the progress of the microservice more carefully. Until the point in time, the microservice is stable enough to take over the entire workload of the monolith’s functionality.
- Feature flag: As mentioned above, we will apply the strangler pattern to control the evolution of the microservice. This requires updating the legacy code to call the new microservice. A best practice for this task is the use of feature flags that allow to control the integration of the new service without redeploying the monolith.
- Database virtualization: A transformation of a monolith to microservices has a strong impact not only on the code base, but also on the schema of the legacy database. Since each microservice should have its own datastore, it is required to retrieve data from the monolithic database as well as from the database that belongs to the service. To work with this two data sets in a jointly manner, we will use a database virtualization technique temporarily during the process of the migration.
In the next part of this series, we will walk you through setting up our monolith TicketMonster application. Afterwards, we conduct our first step of our monolith to microservice showcase that focuses on extracting the UI from the monolith and performing a canary release. Follow our journey by subscribing to this blog.