📜 ⬆️ ⬇️

We saw monolith


Before this opus is an extremely difficult task - to describe the principles of the transition from monolith to microservices, while not telling the reader what he already knows. Microservice architecture is so popular that the author is firmly convinced that every second reader has worked with such architecture or hates it with all the fibers of his soul. There is also an opinion that most of the developers reading these lines joined the project built on microservices after it became microservice. And that small part of the guys who independently made the way from the monolith to microservices are already experienced enough to work as senior developers, architects or project managers.


If it is the time for the application to move from absolute monarchy to fragmented feudalism, then most of the time will be spent on returning the technical debt accumulated over the years. And this debt will need to be returned by honor - you need to return what you owe and what all your predecessors owed. For this process, there is no concept of "mine" and "not mine", but there is only the concept of knightly valor of developers who are not lucky enough to be in the project at the moment. The programmer must be a real vassal of his overlord and rush into battle with an army of architectural errors, lack of tests and a heap of antipatterns.


There are a lot of unsuccessful examples of the transition to microservices, in which the existing code has not changed as much as possible. But the new functionality was written in separate repositories, which used the main existing code as a black box. As a result, there was a huge amount of duplication of functionality, duplication of data and the devil knows what else. The main project was no longer maintained, its dependencies were hopelessly outdated. The task in such projects was to write a sufficient number of microservices around the main project in order to fully cover all the functionality and work outside only through services. And just as this moment was achieved, according to the plan, it was necessary to start replacing the functionality of the black box with newly written microservices. As a rule, such applications never went beyond the dependence on the main legacy code.


Of course, everyone can say "refactor", for this you do not need to be a rocket scientist. A little harder, but still trite, to start talking about the ways of refactoring, listed in one hundred and one books, written by Fowler and Fowler fans and his haters. It is completely useless to start showing huge and complicated examples of how not to do and ideally chosen spherical vacuum examples that are completely inapplicable in practice. Also it is worth considering that those guys are programmers who create problems that have long outgrown themselves and no longer do that. Or, on the contrary, they can never even think of reading articles of this kind.


It is also noticed that to consider the ideal code and the ideal project is boring and not interesting. In our perfected, analytically arranged programmer heads, we always know how the code should look. We can always with particular accuracy and cynicism be able to distinguish bad code from horrible code. Sometimes, however, we fall into a logical trap exclaiming "what kind of idiot wrote this ?!", looking at his own code of half a year ago. By the way, this only confirms the high level of professional growth of the developer over this very half year.


In addition, the author does not intend to fall into a logical trap, arguing with the reader in matters in which the reader is a priori better versed. And therefore, before we begin to make loud statements, we will agree that further hypothetical projects are considered further with one important common feature - they are written out of it very badly. Well, just awful. You need to close your eyes and imagine the most terrible spherical project in a vacuum that you can imagine. Of course, he, like any other self-respecting enterprise project, works, and works quite stably and, most importantly, makes a profit. He has everything that such a project should have: just customers, satisfied customers, technical support and a nice view from the outside. That's just from the inside, it is terrible and only valiant knights-programmers know about it, ready to refactor, as no one has ever refactored. From somewhere the author is confident that an experienced developer will present this without much difficulty.


But still, some strategies and rules of purity of the code should be mentioned. The first goals of improving the code and preparing it for microservice life will follow from these rules.


Microservice planning


A favorite business of all the wrong managers and senior developers is to plan for and without cause. But the author does not call for abandoning planning at all - the lack of competent planning in the project makes the development look like an elk rushing through a burning forest. But the process of long-term planning from the point of view of transition to microservices is extremely ungrateful. You can more or less accurately say how to take a single class or library to refactor, but it is very difficult to think out the entire microservice architecture at an early stage and guess. The task of such early planning is to divide the project into several logical parts without any reference to the code or the current architecture. In fact, it is necessary to revise what the application does after all and which of these functionalities are independent. And return to this list every Monday morning for a cup of hot coffee, reviewing the written set of functions and referring to what you want to strive for.


Global variables and singletons


Why it is bad to use variables with a global scope that every boy knows. Even with mother's milk, each developer absorbs the knowledge that storing and changing such data here leads to problems with competitiveness and scaling. Of course, this is true. But from the point of view of refactoring, global variables are bad because it becomes impossible to bring some functionality to a separately configured server without duplication. We should also mention the libraries that have globally configured objects. For example, a Ruby-library for working with a docker by default offers to configure access to the docker-environment through a globally available singleton:


Docker.url = 'tcp://example.com:5422' Docker::Container.create({ 'Cmd' => ['ls'], 'Image' => 'base' }) 

Work with globally accessible variables


Of course, this kind of setup saved the initial connection of this library, but made it impossible to scale horizontally, working with several docker-hosts at the same time. In such a project, working with a docker has penetrated to all levels of abstraction and it is not possible to regulate the choice of a docker-host for a single request. Fortunately, the creators of this library provided the opportunity to work with several hosts. Each command has an additional optional parameter in which you can define a host:


 connections = [ Docker::Connection.new('tcp://example.com:2375', {}), Docker::Connection.new('tcp://example2.com:2375', {}), Docker::Connection.new('tcp://example3.com:2375', {}) ] Docker::Container.create({ 'Cmd' => ['ls'], 'Image' => 'base' }, connections.sample) 

exaggerated simplest implementation of the Round-robin algorithm between multiple servers


The simplest and most obvious way to abandon global objects is to go over simple formal-factual parameters. The object used on several layers of abstraction must be declared a local variable at the highest of the used layers, and passed through formally-actual parameters to all places where a global variable was used before.


But be careful. The flip side of this approach is the case in which the number of formal parameters is already huge. In such cases, one hundred and one book of correct refactoring recommends that you approach this process pragmatically - putting part of the functionality into separate classes, working with a set of parameters, as with a single data structure, highlighting methods and other right things.


Proxy Classes and Proxy Methods


The next milestone in the transition to separate services from a monolithic application will be the principle of the division of responsibility and the principle of a single duty . At this stage, you need to constantly keep in mind that it is completely incomprehensible what services will be obtained as a result of this long and painstaking journey and not immediately strive to make separate and independent pieces of code. It is possible for a long time and with the taste of an expert to retell the books of Martin and Demarco, giving the read as his own thoughts, but the authors of these books make this much more concise. As a result, the code that does not comply with the principle of sharing responsibility is extremely difficult to bring into a separate service, even if global variables and singletones are already missing.


Libraries


Those libraries that are well-designed, have no significant problems and do not need to re-read their source code on every occasion are considered good. And it so happened that working with well-written third-party libraries brings us undisguised pleasure to us, programmers. And libraries that require constant intervention, an unobvious subject-oriented language and a bunch of bugs, make you want to throw the entire library to hell and write your own solution. Most often, this proprietary solution is almost as uncomfortable and not obvious. And, as a rule, the considered type of applications consists of a huge number of its own solutions to standard problems.


As a result, the next step in the transition from whole to fragmented will be the abandonment of manual solutions, and the transition to popular well-written open source libraries. Of course, it may be that the required functionality is simply not available as a library. In this case, the decision will be to make such a library and open the source code for the community. In the future, this kind of monolithic application refactoring will allow the use of common functionality in different microservices.


There is an opinion that making an open source library is quite difficult, and it takes a lot of time to devote to this process. Documentation, full test coverage, annotations, support for service files, release descriptions, and a lot of non-programmer work. It is extremely difficult and tiring. In addition, the code that is readable by anyone makes the developer be sensitive to what is being written and most developers prefer not to show their code to others, being afraid of being condemned for bad code or ugly decisions. The author does not have a common solution, but he knows only the analogy of a knight and a suzerain. Knights do not ask how interesting they are to do this or that job. It is necessary, then it is necessary.


But putting code into general-purpose libraries has several additional obvious advantages. Using these libraries in third-party applications will allow you to get rid of bugs that have not yet been found, make sure that the library works in different environments and will allow it to be developed by third-party developers.


Also, do not go too far and carry everything to the library. The application logic must remain within the application itself. A good criterion for putting some code into a separate library is the question asked whether it is possible to use this library in applications with a different theme and business logic.


Simple data


At this stage, application areas should already be formed with explicit entry points and a communication interface between neighboring areas. The task of this stage of refactoring is to get rid of complex structures in formal factual parameters. Of course, a thoughtless rejection of class structures in favor of hashes and data arrays will not save the situation, but will only aggravate it. A function with sixteen formal simple parameters, though simple from the point of view of serialization, is incredibly complex from the point of view of interaction with it. A formal parameter of a complex type can easily be designed as a separate service, library, or independent subroutine.


The goal of simplifying formal-factual parameters is the possibility of making a single sub-application into an independent system process. And communication with such a process will need to be made out in the form of standardized language-independent protocols.


Common Standards


Protocols have come up with a whole set for every taste and color. For popular ways to communicate in almost all languages, there are quite acceptable solutions, and implemented with little blood. After completing all the previous instructions, the application will finally be ready to make its individual parts into separate subroutines or services. The main criterion for the selection of a separate standard for communication between applications will be the ability to easily replace, summarize, expand or improve it.


It is a mistake to believe that the choice of one single publicly available standard for communication between all microservices will mean the unification of the entire application. On the contrary, the unification of communication between services can force individual microservices to tune the architecture for the better and not for the benefit of the application. Communication standards between services may vary and depend on specific requirements and conditions. It is necessary to choose the standard slowly and with the arrangement and it is critical to approach the advantages and disadvantages of this or that standard when choosing one for the new microservice. Of course, the fact that a standard is already used in individual microservices gives it an advantage over the rest, all other things being equal, but this criterion should not be the only one taken into account.


Instead of conclusions


The transition to microservice architecture is a rather painstaking process. And do not assume that after the transition to architecture, with the promises of a rainbow life, everything will really be rosy in the project. Miracles do not happen. In addition, it is not necessary to take this article as a call to translate absolutely any project on microservice architecture . Not all projects are ready for this.


And do not confuse a causal relationship. This article tells you what steps you need to take if you are determined to link the future of the project with microservices. And by no means the opposite. This article is not a call to use microservices in your project.



')

Source: https://habr.com/ru/post/308372/


All Articles