I recently wrote about the differences between monoliths and microservices and which you should pick when designing a new system. In most cases, companies will start with an existing monolithic application that they want to break down.
As engineering challenges go, breaking apart a monolith into microservices can be a daunting one. I have been in charge of breaking apart a few legacy applications in my career. In this article, I will share all the lessons I have learned.
The first step is to analyse everything that your application is doing. It is important to understand all the responsibilities that your monolith has accumulated over time.
This is usually done best as a white-boarding exercise with both product and engineering. If you are working remotely then Miro is also a good choice.
It can be easy to spend too much time on this exercise by outlining every single endpoint and the various services and databases that it touches. At this stage, I would advise against that, as you will experience diminishing returns by going into too much detail at this point.
What you come up with should be understandable by the whole team. Stick to high-level functionality and outline the key parts of the application.
Don’t forget to include supporting functionality as well, such as auditing.
Auditing is especially important if you work in a regulated industry that has strict auditing requirements. You don’t want to break apart your monolith and realise the new services are not auditing properly.
One of the key goals of splitting apart a monolith is to be able to release them independently.
The last thing you want is to have a spaghetti mess of dependencies, that has teams working across many repositories and releasing multiple components with every release.
Using Domain-Driven Design can be helpful when working out what a good domain looks like, so I would advise doing some reading on the topic. It is a difficult subject but having at least some knowledge will be beneficial.
Start by outlining the supporting functionality that most applications have such as:
We often follow the Don’t Repeat Yourself (DRY) Principle when writing code for an application but we don’t often follow it across multiple applications.
Moving these supporting functions into their own microservices can allow other teams to use them, saving them from having to reinvent the wheel each time. There are often a host of other benefits that come from having these centralised as well.
A key concept of microservices is that every service is responsible for its own database. This can be one of the hardest concepts to get your head around when moving from a centralised database structure.
It is important to think about data ownership when outlining your domains.
Two microservices can’t share the same database, access to another microservices data has to be via that microservice’s API. If you take a look at your database tables you might be able to outline some domains from tables that are regularly joined together with others.
When one microservice does need to reference data from another service it should use an ID. For example, services can reference a UserId but the “source of truth” of the user data will remain in the user microservice.
When moving from a monolith to a microservice architecture you should do everything possible to avoid a big bang 💥. This is where you completely switch from one architecture to another.
The better approach is to make incremental changes towards your ideal architecture.