Onboarding a legacy project mix monolith and microservices (2/2)
You found out that, yes, you have to break a monolithic application and transform it into a microservice architecture. How can we do that?
In the first chapter of this series, I’ve shared with all of you the very first steps when you, as a brand-new Tech Lead go to a new company to make their system “better”. If you haven’t read it yet, here you have 👇🏻
Anyway, let’s remember the initial questions from that very first issue:
Where would you start to transform the monolith into microservices?
Would you refactor gradually, or would you start new microservices from scratch?
Would you refactor and improve the testing of the monolith before making any changes?
In today’s issue, we will cover in detail the answers to all those questions. Assuming that, based on your previous analysis, you have indeed concluded that you need to get rid of that monolith (although there are occasions when it's not necessary).
First, in my opinion, the 2 first questions are the same. Basically, the question here is: should I hammer that monolith in one shot? I will be bold here: I do not recommend replacing the whole monolith in one shot. Now, let’s move to the details about how to address this challenge.
🔨 Hold your hammer, use the scalpel
From the technical roadmap, that you defined at the beginning (read the first issue of the series), you might have something like “explode the monolith”.
My recommendation at this stage is to not go wild and avoid starting to create microservices to replace the entire monolith all at once.
Instead, first, identify all the functionalities that this monolith holds, put them in a ranking by importance, based on the priorities set in advance, and put in a bucket the ones that will not be needed anymore. It’s important that the functionalities could be as isolated (as decoupled) as possible.
Now, pick up the functionality with more priority and think about how you can deliver it as a microservice and (importantly) how the monolith will interact (if needed) with it. Implement that small functionality and the corresponding integration with the monolith (if needed), ship it, and evaluate the result. Ask yourself if this change goes in the right direction.
Now, iterate this process until your monolith has no more functionalities or it’s refactored to something that has one single responsibility.
With this process the third question mentioned at the beginning of the reading is answered: I would recommend focusing on slicing the monolith instead of investing time in refactoring and accommodating the code before getting rid of it. In my opinion, you would waste not invest your time on doing the right thing.
☣️ Do not distribute the monolith
One of the most common mistakes we make at the time to dismantle an old monolithic application is to create a “distributed monolith”. I would define this concept as:
The set of N-separated applications that all need to communicate with each other to solve 1 single use case.
This is something that happened to me. Let me explain to you what the problem is with this and how to avoid it.
After exploding your old friend monolith, you ended up with 4 different microservices. Then, in order to solve one of the use cases that the monolith was solving, for example, “a customer saves 1 content in the system”, you see that your 4 microservices must work all together to satisfy the use case, as illustrated in the image below. So far, so good, because they are microservices; what could go wrong!?
In this picture, you can imagine either REST API calls, WebSockets, or similar, happening between the services; with their corresponding REQUEST and their corresponding RESPONSE.
Now, let’s imagine that one of the services is performing slowly or, even worse, it’s breaking up with each request.
The system will not work, right? The “Service D” is required to collaborate in the chain in order to satisfy the use case. So, what is the difference between this beautiful microservice architecture and our old friend monolith? To be friendly, let’s say not enough different.
The fact that one single request fails makes the whole use case fail, all services will start to respond with an HTTP 500 error code (or similar) as it could happen on the monolith. Also, most likely the system will be slower than when we had the monolith, just because now we have multiple requests/responses (network traffic, your old-new enemy!) when, in the monolith, everything was happening in the same process.
🤯 Yeah, I know; you see now that all the effort made to explode the monolith into microservices ended up with the same problems as having the monolith, or even worse because now we have different code bases and probably different ownership for the microservices. No worries, here’s the lesson I learned.
One change you have to make in your mind when you move to a microservice architecture is that your system must be loosely coupled and, potentially, asynchronous. This means some things:
The number of dependencies for a microservice should be as reduced as possible. In our example above, the “Service A” communicates with 2 different services. Maybe that’s too much.
Your microservice must do 1 thing, and its concern must be to just honor the contract with the API that is communicating with.
Use async APIs (pub/sub approach) as much as possible, to ensure that you do not suffer a cascade issue if some of the pieces of your architecture is malfunctioning. Using domain events approach might help with this.
The way to have a loosely coupled system or architecture is to have a very clear idea about the Domain of each functionality and, from there, define the contract (the API) for the communication between them.
Summary
Let’s wrap up the issue by highlighting the most important takeaways. If you found out that, yes, you have to break a monolithic application into a microservice architecture, keep this in mind.
Extract functionalities little by little, ensuring the system remains stable and performant.
Reduce the dependencies for each new microservice as much as possible. Ideally, 1 single dependency.
Play in asynchronous mode, instead of synchronous mode, as much as your business logic allows it.
Hope you enjoyed this series of 2 issues dedicated to “Onboarding a legacy project mix monolith and microservices“. I would love to hear your feedback about whether you like or not to read this kind of “series” of issues, and also about this issue itself!
Best,
Marcos.