Refactoring monomers into microservices

Overview of microservice refactoring

The process of converting monolithic applications into microservices is a form of application modernization. This is what developers have been doing for decades. Therefore, when refactoring applications into microservices, there are some ideas that can be reused.

One strategy not to use is "Big Bang" rewriting. It means that you concentrate all your development work on building new microservice-based applications from scratch. Although this sounds attractive, it is very dangerous and it may fail. According to AsMartin Fowler: " The only thing a Big Bang rewrite guarantees is a Big Bang!" ("the only thing a Big Bang rewrite guarantees is a Big Bang!").

You should refactor the monolithic application gradually, not through the big bang. You can gradually add new functions and create extensions of existing functions in the form of microservices—modify the monolithic application in a complementary manner, and run the microservices and the modified monolith together. Over time, the amount of functionality implemented by a monolithic application will shrink until it disappears completely or becomes another microservice. This strategy is similar to driving a car on a 70 km/h highway, which is very challenging, but it is much less risky than trying to rewrite the big bang.

Martin Fowler calls this application modernization strategy the Strangler Application. The name comes from the vine (also known as the strangulation banyan) found in the tropical rain forest. A vine grows on a tree to get sunlight above the forest canopy. Sometimes, the tree dies, leaving a tree-shaped tent. Application modernization also follows the same pattern. We will build a new application that includes microservices around legacy applications (it will slowly shrink or eventually die).

Let's take a look at the different strategies that can do this.

Strategy 1: Stop mining

The law of caves says that whenever you are in a cave, you should stop digging. This is a good suggestion when your monolithic application becomes difficult to manage. In other words, you should stop expanding and avoid making the monomer larger. This means that when you want to implement new features, you should not add more code to the monolith. Instead, the main idea of ​​this strategy is to put the new code in a separate microservice.

After applying this method, the system architecture is shown in Figure 7-1.

Implement new functions as separate services instead of adding modules to the monolith

In addition to new services and traditional monoliths, there are two other components. The first is request routing, which handles incoming (HTTP) requests, similar to the API gateway described in Chapter 2. The routing sends a request corresponding to the new function to the new service. It routes the remaining requests to the monolith.

Another component is glue code, which integrates services with monoliths. A service rarely exists in isolation, and usually needs to access single data. Glue code in a single, service, or both is responsible for data integration. The service uses glue code to read and write individual data.

Services can use three strategies to access monolithic data:

  • Call the remote API provided by the monomer
  • Direct access to the monolithic database
  • Maintain your own copy of data and synchronize with the single database

The glue code is sometimes called the anti-corruption layer. This is because the glue code prevents services from being contaminated by the concept of a legacy single domain model, which has its own original domain model. The glue code is converted between two different models. The term “protection layer” first appeared in the must-read book " Domain Driven Design " by Eric Evans and was improved in the white paper. Developing a protective layer is not a simple matter. However, if you want to get out of the single hell, this is an essential step.

There are several benefits to using lightweight services to implement new features. It prevents the monomer from becoming more difficult to manage. The service can be independent of monomer development, deployment and expansion. Let each new service you create experience the advantages of the microservice architecture.

However, this method does not solve the monomer problem. To solve these problems, you need to decompose the monomer. Let's take a look at the strategy for doing this.

Strategy 2: Separation of front and back ends

One strategy for shrinking monolithic applications is to separate the presentation layer from the business logic layer and the data access layer. A typical enterprise application consists of at least three different types of components:

  • The Presentation Layer (PL) processes HTTP requests and implements (REST) ​​API or HTML-based Web UI components. In applications with complex user interfaces, there is usually a lot of code in the presentation layer.
  • The Business Logic Layer (BLL), as the core of the application, is the component that implements business rules.
  • The Data Access Layer (DAL) accesses components of the infrastructure components, such as databases and message brokers.

There is usually a complete separation between the presentation logic of one party and the business and data access logic of the other party. The business layer has a coarse-grained API composed of one or more facades, which encapsulate business logic components. This API is a natural boundary along which you can split the monolith into two smaller applications. An application contains the presentation layer. Another application contains business and data access logic. After segmentation, the presentation logic application makes remote calls to the business logic application.

The architecture before and after the reconstruction is shown in Figure 7-2.

Refactor existing applications

Splitting the monomer in this way has two main advantages. It enables you to develop, deploy, and scale these two applications independently of each other. In particular, it allows presentation layer developers to quickly iterate on the user interface and perform A/B testing easily. Another advantage of this approach is that it exposes remote APIs that can be called by the microservices you develop.

However, this strategy is only a partial solution. One or both of the two applications are likely to be an unmanageable monolith. You need to use the third strategy to eliminate the remaining whole or unit.

Strategy 3: Extract service

The third refactoring strategy is to transform huge existing modules into independent microservices. Each time a module is extracted and converted into a service, the monolith will shrink. Once you have converted enough modules, the monolith will no longer be a problem. Or it disappears completely, or becomes small enough that it can be treated as a service.

Which modules should be converted into microservices first

A large and complex single application consists of dozens or hundreds of modules, all of which are candidates for extraction. Figuring out which modules to convert first often presents certain challenges. A good approach is to start with a few modules that are easy to extract. You will get relevant experience in microservices, especially in the extraction process. After that, you should extract those modules that give you the most benefit.

Converting modules to services is usually time-consuming. You want to arrange the modules according to the benefits you will receive. It is often beneficial to extract frequently changed modules. Once the module is converted into a service, you can develop and deploy independently of the monolith, which will speed up the development work.

It is also beneficial to extract these modules that are significantly different from the other modules of the monomer. For example, it is useful to convert a module with an in-memory database into a service, so that it can be deployed on a host with a large amount of memory, whether it is a bare metal server, a virtual machine, or a cloud instance. Similarly, it is worthwhile to extract modules that implement computationally expensive algorithms, because the service can be deployed on a host with a large number of CPUs. By converting modules with specific resource requirements into services, you can make your application easier and cheaper to expand.

When finding the modules to be extracted, it is useful to look for existing coarse-grained boundaries (also known as seams). They make it easier and cheaper to convert modules into services. An example of such a boundary is a module that communicates with the rest of the application only through asynchronous messages. It is relatively cheap and simple to turn this module into a microservice.

How to extract modules

The first step in extracting a module is to define a coarse-grained interface between the module and the monomer. Because the monolith needs the data owned by the service, it is likely to be a two-way API, and vice versa. Due to the complex dependencies and fine-grained interaction patterns between the module and the rest of the application, there are often challenges in implementing such an API. Due to the numerous associations between domain model classes, the business logic implemented using domain model patterns is particularly challenging. You usually need to make major code changes to break these dependencies. Figure 7-3 shows the refactoring.

Monolithic modules can be converted into microservices

Once the coarse-grained interface is implemented, you can turn the module into an independent service. To do this, you must write code so that the monolith and the service communicate through an API that uses the inter-process communication (IPC) mechanism. Figure 7-3 shows the architecture before, during, and after refactoring.

In this example, module Z is the candidate module to be extracted. Its components are used by module X, and it uses module Y. The first refactoring step is to define a pair of coarse-grained APIs. The first interface is an inbound interface that uses module X to call module Z. The second interface is an outbound interface that uses module Z to call module Y.

The second refactoring step is to convert the module into an independent service. The inbound and outbound interfaces are implemented using IPC mechanism code. You will most likely need to build services by combining Module Z with the Microservice Chassis framework, which is responsible for handling crosscutting points such as service discovery.

Once you extract a module, you can develop, deploy, and extend other services independently of the monolith and any other services. You can even rewrite the service from scratch. In this case, the API code of the integrated service and the monolith becomes the protective layer for the conversion between the two domain models. Every time you withdraw a service, you will take a step towards microservices. Over time, the monolith will shrink and you will have more and more microservices.

to sum up

The process of migrating existing applications to microservices is a form of application modernization. You should not rewrite your application from scratch to migrate to microservices. Instead, you should gradually refactor the application into a set of microservices. These three strategies can be used: implement new functions as microservices; separate presentation components from business components and data access components; convert existing modules in the monolith into services. Over time, the number of microservices will grow, and so will the flexibility and speed of your development team.


Guess you like

Origin blog.51cto.com/2096101/2679701