III. Good migration principles
1) Minimising dependencies between the new microservice and the monolith.
The presence of such dependencies may make it necessary to perform changes on the monolithic side. As we know, these changes are expensive and happen slowly, among other things due to the long release cycle of a monolithic application. Since by introducing microservices we want to make their release cycle independent of the monolith, introducing dependencies of this type is highly undesirable. However, dependencies may appear in the opposite direction, i. e. the monolith may use new features provided by microservices. This is the correct direction of created dependencies, which does not slow down the pace of change.
For example: an e-commerce system contains a sales module and a pricing policy module. The sales module obviously uses the pricing policies set in the system. Therefore, in order to not create a microservice dependency on the monolith, it is advisable to migrate the pricing policy module first and only then the sales module. In this way, in a transitional state, the sales module in the monolith will use the new microservice with pricing policies, and ultimately will also be migrated to a separate microservice.
However, there are cases when the dependence of the microservice on the monolith cannot be avoided. Then, a good solution is to add the so-called anti-corruption layer on the monolith side. This layer translates the communication between the old and the new system, which on the one hand does not impose many changes on the monolith side, and at the same time ensures semantic consistency.
2) Data layer migration should not be neglected.
It is important to remember that the main purpose of separating new microservices is to achieve independence from the old monolith and the possibility of completely separate development and deployment of new services. Leaving dependent elements on the monolith side, such as stored data in a common database, will not achieve this goal. Keeping all data in one database goes against the principle of decentralised data management applicable to microservices architecture.
3) The priority is to isolate those parts of the system that change most frequently.
System components that are subject to frequent changes are great candidates to be extracted from the monolith and to be moved to a dedicated microservice. This will result, on the one hand, in less pressure to further develop the monolith and, on the other hand, in more flexible changes that will only take place within the designated microservice.
4) Consider whether rewriting the code of the migrated elements is a better idea than refactoring it for the new microservice.
Writing a migrated system function from scratch seems at first sight to be a more costly solution than moving the old code and refactoring it. However, this impression is not always true. First of all, understanding the existing code requires a time investment. Moreover, the quality of the existing code can be questionable. In addition, technological changes and technical differences between a monolith and a microservice may cause the old code to become largely useless.
Such a decision therefore depends on many factors and should always be considered on a case-by-case basis, with no automaticity in action. Code that incorporates complex domain logic independent of technical layers is probably a good candidate for porting and possible refactoring. In contrast, CRUD-type code or code that depends on multiple technological factors will be better off rewritten.
5) Evolution instead of revolution
Migrated monolithic systems are usually large and have been developed for years. Their migration cannot take place quickly and smoothly. The solution to this problem is evolutionary migration in the form of a series of consecutive steps consisting in extracting parts of the monolith and building corresponding new microservices. Each such step should bring the system architecture closer to the target state.