Introduction This article aims to explore some techniques to migrate your monolithic system to microservices based mainly on Sam Newman's work, the book: Migrating monolithic systems to microservices. Despite trying to consolidate the main lessons learned from the book through this review, I recommend that, at the end of reading, you seek to delve deeper into the subject through the original work. Understanding the context better Firstly, we need to understand the concepts involved, what a monolithic system would be, its advantages and disadvantages, as well as what a microservices system is, advantages and disadvantages.
Monolithic systems The word monolith can be conceptualized as a monument or work made up of a single block of stone. A monolithic system, on the other hand, is that single system, which has at least three types: Single process (most common); Distributed monolithic; And third-party black box systems.
Sam Newman refers to that system as an implementation unit, when all the functionalities of a system must be implemented together.
Monolithic systems have the following advantages: Quickly upload a PoC or MVP to validate a business or product;The development environment is simpler when the architecture is monolithic;Simpler to test, as it is possible to test the application from end to end in a single single place; Low communication between networks; Simpler deployment topology.
This model also has its disadvantages, such as: Difficult to scale; Because it is built on a single code, if something breaks, the whole system can be unavailable; Difficult maintenance given the growth of the code base; Low flexibility over the programming languages of the project.
Martin Fowler says: Monolithic applications can be successful but frustrating – especially when more applications are deployed in the cloud. Development cycles are tethered – a change made to a small part of the application requires the entire monolith to be republished. To the/]. over time it will become increasingly difficult to maintain a modular structure, which makes it harder to make changes affect only one module. Scaling requires scaling the entire application, not just the parts that require the most resources.
Microservices Sam Newman conceptualizes that microservices “are services that can be deployed independently and are modeled around a domain”. The form of communication between them is also important, being through networks. Thus, this microservices architecture is based on several microservices collaborating with each other.
One of the advantages of these, the microservices are technology independent, as they expose the functionalities through endpoints to be consumed through the network, see as a distributed system.
Another advantage and characteristic is that they have independent deployments. This way, you can update (change and deploy) a service without having to update the rest of the system, in other words, forget about the need to orchestrate multiple deliveries from multiple teams so that everything is delivered in a single package. Remembering that, for this to be true, we must guarantee low coupling.
Unlike monolithic systems, microservices can scale independently. This advantage can help in proper distribution of resources, cost reduction and many more.
Furthermore, as the domain is encapsulated and externalizes its functionalities through endpoints, they can be reused in several applications.
I mentioned some advantages, but it is not limited to that. We can work in parallel on services, include more developers to solve a problem without getting in each other's way, we reduce cognitive complexity over business knowledge and much more.
Of course, I sold a dream with several advantages, but not everything is rosy. The model has its challenges. Among these challenges, we can mention communication between them, that is, networks – we have to worry, for example, about latencies, packet delivery failures. Another challenge is consolidating and reading logs for potential debugging. It also has the challenge of dealing with varying stacks across different services.
One of the discussions that always comes up is about the size of microservices. This is one of the lessons learned from Sam Newman's book – don't worry about the size, or number of lines of code, but about the business. In the book, he focuses on understanding the business domain well and that is why he suggests the use of DDD (Domain-Driven Design). I'm not going to go into the merits of DDD, as it's not the focus of this article, but it's certainly worth studying this topic in more depth.
Techniques for migration Before any migration, it is important to know the reason for it. What you want to achieve with this change, have a clear vision, as this change may require a significant investment. Furthermore, it's normal to take a transition like this, it's not just a matter of the sunk cost fallacy, but people actually forget why they're doing the work.
Not least, many of the benefits achieved with a microservices architecture can be achieved in other ways, I will give just one example. The desire to increase the autonomy of teams is frequent, it increases confidence, motivation, freedom and many other benefits. How to do this? One possible path is to have different teams be responsible for different parts of the system. In this case, a modular monolith helps a lot. Another way is to identify people with greater knowledge about certain parts of the system and empower them with decision making about that part. One more way to increase autonomy can be by adopting self-service approaches for provisioning machines, environments, access to logs.
Finally, it is important to know when not to adopt microservices: In cloudy domains; Startups; Software installed and managed by customers; And when there is no good reason!
But let's say you are convinced that the best path forward for you is to actually migrate your monolithic system to microservices. So let's talk about some techniques.
strangler fig application This is a technique often seen in system rewrites. The pattern, initially identified by Martin Fowler, is inspired by a type of fig tree that germinates in the upper branches of trees. The existing tree serves as a support for the new fig tree, which, if it reaches the final stages, it will be possible to see the original tree die and rot, leaving only the new fig tree.
Looking at software, the idea is that the new system has the support of the existing system, allowing coexistence, and that, when the new system is ready, we can replace the old one.
This pattern is based on three steps: Identify the parts of the current system you want to migrate – one of the ways to do this is with a cost-benefit analysis; Implement the functionality in your new microservice; Divert calls from the monolithic system to the new microservice.
Diagram based on figure 3.1 from Sam Newman's book I draw attention to the fact that, until the call for the new functionality is made, it will exist in a production environment, but will not be technically active. This way, you have time to develop the new functionality until you are satisfied with the new implementation and management of the service.
One of the ideas here is to separate the concept of deployment from release. This does not mean that the functionality, because it is in the production environment, will actually be used by customers. This gives you the freedom to test new functionality in production before it is used. Another important point is that this gradual migration approach allows us to rollback much more easily.
UI Composition The previous technique was focused on a server-side migration, but the user interface also provides great opportunities. Imagine functionality partly served by the monolith and partly served by a new microservices architecture.
One of the strategies widely used in migrations is composition by pages. Instead of migrating everything at once, pages are being updated, which in a moment of transition will present different experiences for users when using new parts of the system or site.
Another strategy is to do this by composing widgets. In Sam Newman's book, in one of his cases, the idea was to take a part of a website, with interesting challenges, but which was also not the most prominent part of the system. The idea was to put something out there, learn from the experience and make sure that, if there was a mistake, it didn't affect the core of the system. The example presented is of a travel system, which decided to just deploy a single widget that displayed the top ten travel destinations defined by the new system. Here, the Edge-Side Includes technique was used, using Apache, where a template was defined in your web page and a web server provides the content.
UI composition allows you to separate distinct UI modules that could represent a search form, a map, etc. The figure below illustrates the idea.
Figure based on figures 3.20 3 3.21 from Sam Newman's book Another path, rather than the composition of multiple pages, was that of single-page applications, having a more efficient interface and running everything in a single panel. Here, the idea is a composition of widgets. There were attempts at common formats, one of these attempts was through the Web Components specification, but the delay for this standard to gain strength caused people to look for alternatives. An alternative that has gained a lot of traction in the world of Javascript frameworks such as Vue, Angular and React is Micro Frontends. The term Micro Frontend gained strength in 2016, proposing the same idea of microservices that were used in the backend, only this time for the frontend. The principles that support the microfrontend are: Be agnostic in relation to technology; Each service be isolated and self-contained; Agree on nomenclatures for local storage, cookies and others to avoid conflicts; Prioritize native browser resources over custom APIs; Build a resilient website and usable even when you have problems loading Javascript code.
Branch by abstraction Previously, we talked about the strangler fig pattern, in which it intercepts calls on the perimeter of the monolithic system. But what if the functionality we want to extract is too deep-rooted? We want to make changes without causing problems for the system, much less for the developers working on that code base. This leads us to want to make quick changes in order to avoid disruption.
Normally, we develop code in branches and, when these changes are ready, we merge them to master. The longer that branch exists, the greater the challenges of merging. So, the idea here is to be able to develop code incrementally, without major disruptions and without using a long-lived code branch. Branching by abstraction allows you to make changes to existing code to allow implementations to coexist securely.
The pattern has 5 steps: Create an abstraction for the desired functionality; Change the calls to use the new abstraction; Implement the abstraction with the reworked functionality, which will call the new microservice; Switch the abstraction so that the new implementation is used and,Clean up the abstraction and remove the old implementation.
Below, I try to illustrate what each of these steps would look like. Imagine that we have a tax system that deals with the NF-e (electronic invoice), the titles generated, bookkeeping, adjustments, transfers, returns and finally generating a calculation.
The first step would be to select the functionality to be replaced and create an abstraction. One of Sam Newman's lessons is to try to start with features with a low number of inputs and outputs. Therefore, the NF-e and the Apuração would be the last to be chosen. In this example, I'll choose bookkeeping to start decomposing our monolith into microservices.
Step 1: Create abstraction Step 2: Use abstraction Step 3: Create another implementation Step 4: Switch implementation Step 5: Clean up Conclusion In this article, I've talked about a few techniques for decomposing your monolithic system, but there are dozens more shapes. In Sam Newman's book he shows other techniques, such as: Parallel execution; Collaborator decorator; Modified data capture; Not to mention database-related decompositions, which were not even mentioned in this article.
It is important to know that, in a real project, a mix of techniques will normally be necessary to be successful in decomposing a monolithic system into microservices. Another great learning was that you don't need to do a migration like this to get many of the benefits of a microservices architecture, so be clear about the reason for doing work like this.
Remember that only what you measure is managed. So, be well equipped with indicators to monitor the project's progress and success given the defined objectives.
Finally, understand that it is necessary to involve people in such a migration process, they are fundamental to the success of the project.