The journey of breaking a monolith into microservices
By: Guy Heimann
Tags: Monolith, Decoupling, Microservices, Software Architecture

The world is moving from monolithic architecture to microservices architecture. For an organization with production legacy code to go down that path is a huge architecture decision and the opening shot of a very long journey. I’m not going to explain which architecture is the best for different cases, I trust that if you are already here you know what’s best for you.

Each organization has its own reason to move to microservices. The common ones are:

Faster adoption of new technologies.

Shorter release cycle.

Cloud scalability.

The reason can sometimes distract us from the whole picture and make us focus on specifics. That would be a mistake. When starting this journey, it’s recommended to have a high-level design of microservices architecture for the application with the understanding that the suggested design is answering the initial need. In other words, the main reason that led us to this change.

I was part of this journey more than once, and each time it’s a little different because each monolith is one of a kind. But there are still a lot of similarities between the cases. This can be translated into guidelines for the journey.

Decoupling

Usually, the high-level design will reflect the business logic components from the existing monolith. At this point you’re probably saying to yourself, “the monolith is well designed, we have a high-level design for microservices that reflects that, what’s the big deal here?” Well, it’s not that simple.

A monolith, no matter how well designed and how well coded, is by nature two sources of coupling.

Common code between the different components

Usually, a monolith will have an area in the code that won’t contain business logic, it will contain utilities. When talking about utility codes that are in common use between multi components, we’re not talking about microservices candidates, but on potential libraries.

The main difficulty with decoupling the common code is that in many cases this code area becomes a black hole for “lost code” and this black hole situation has a huge effect on the decoupling process.

I saw common code packages with no boundaries at all and in other cases some of the common code was not part of the original design and it’s just “thrown around”. Before starting to extract a library out of a monolith, it’s very important to define what the responsibilities of this library are and to find the coupling points of the current code.

Blurring boundaries — the code is at the wrong location

The meaning of blurring boundaries is, on the one hand, that one business logic component uses abilities of another component (instead of moving this ability to the common area) and on the second hand that the common code implements different behaviors depending on the caller (instead of one generic behavior and implementing of the different behaviors on the caller side).

Before starting to take a component out to a microservice, we MUST understand where it’s coupled with other components or with the common code. If we’ll find the coupling points after we started the process, it will harm the project timeline. It may cause a change in the original design, an extra unplanned effort or even the understanding that it will be better to postpone the current microservice development and start with some pre-requisite. Imagine that you are having a surgery in a hospital and at some point during the process the doctor realized that the analysis was wrong and now you need two more surgeries, one to fix the damage from the current redundant surgery and another in order to fix the original issue. So, in this analogy we are the doctors and the code is our patient. Let’s treat it well.

The coupling points are like dependencies between components. Therefore, after you have the full coupling picture, create a coupling chart for yourself (the same as you would do for a dependencies chart) so it will be easy to plan the journey.

For example:

Let’s take Service A as an example. If I would just start and implement Service A, without the coupling chart, during the development process I would find out that I need Libs 1&2. If I would find the way to squeeze it in my schedule, Lib 3 will surprise me on the way and then Lib 4. All of these surprises will increase the project estimation a lot and maybe will also cause changes in the original design.

What I would like to do here is to start from Service A dependencies that doesn’t have any coupling with other dependencies (Lib 1 & Lib 4), then go backward, step-by-step, on the chart until you can implement the microservice. Some of the work can be done in parallel if all are ready in time for integration.

Following this method, for a scenario when the microservices priority for us is Service A > Service C > Service B, the implementation order will be very simple:

Lib 1 & Lib 4 -> Lib 3 -> Lib 2 -> Service A -> Lib 5 -> Service C -> Service B

Remember that in a real-life scenario, you’ll probably have much more services and libraries to analyze and implement. However, the similarities will be that for the first microservices you’ll need to implement a mass of libraries and it will get thinner with time because as long as you go into this journey, you’ll have more “ready to use” libraries.

New functionality

After the starting point of this journey, there is no turning back. Therefore, if the journey is already started and we want to new code into our application we need to be clever about it.

If it’s new functionality, it’s highly recommended to take this opportunity and add a new microservice to the architecture. This new microservice can be responsible for this new functionality only or for other existing responsibilities (to break out of the monolith) if it makes sense. Implement the new functionality will increase the coupling points inside the monolith and will make it more complicated to break it out in the future.

If it’s a new utility for the common code, the consideration is the same, just replace microservice with a library.

If it’s a bug fix, do it in the monolith. No harm will happen.

Conclusion

Breaking a monolith is a long and complicated journey with surprises along the way. All we can do to make it easier is to avoid surprises as much as we can by planning. Remember the main goal, at some point the monolith will go away and we’ll have an application built from microservices only.