Every day, more and more organizations move towards a DevOps way of working. They are heavily investing to build small applications, components or services which are specialized to a dedicated function. However, a lot of these organizations still feel the burden of their old monolithic applications. Those need to be broken down into microservices to deliver changes faster and more reliable. Breaking it down is easier said than done. In this article, I will highlight a couple of tips and tricks which help you with strangling the monolith.
Characteristics of a monolith
Before you fire up your IDE to search for sloppy code to break the monolith down, let’s define some of the common characteristics one. Organizations that face one or more of the following challenges are dealing with a monolith:
- End-users have to wait too long for new features to be released. It takes weeks or months to release a new version.
- The application depends on several development teams (e.g. more than 50 people). A lot of collaboration is needed to even change a simple feature.
- Don’t touch it! Just like a “snowflake server”, no one dares to make a change since that can quickly break a critical feature of the application.
- Only a few people know how to properly debug issues since it’s a huge challenge. In addition to this, only senior developers know how things are set up long time back. This is a real risk when they leave the team.
- Adding new features takes more time every month. It’s (nearly) impossible to adopt new technology.
- The entire code base is relatively large: let’s say more than 100.000 lines of code. A lot of code has so called “code smell” and uses legacy technologies.
Based on this list, organizations should make a plan to not only split the source code of the monolith but also change the organization itself.
A lot of people on the internet share the following concepts which characterize microservices:
Microservices consists of small services which are deployed and maintained independently. Furthermore, they don’t have any (external) dependencies and communicate to each other using lightweight API calls. In order to do this, they do not depend on centralized infrastructure.
Every micros-service has its own version and lifecycle and can be written in its own language. A microservice is dedicated to a specific feature. Other components that rely on it should not know the implementation details.
Keep in mind, there is still a heavy discussion on these concepts and what is in scope and out scope for microservices. This happens on the internet as well as within organizations itself.
Strangling the monolith
Given these conditions, let’s start to touch various topics that are important here.
Changing an organization is much harder than implementing a tool or switch to a new programming language. Therefore it is also hard to change the organizational culture to get rid of a monolith and start embracing microservices. From a developer perspective, the scope changes a lot.
Developer teams that are used to monoliths oversee the entire process of the complete application: from design to development, to deployment and end-to-end tests. Perhaps they see the source code of all of it on a daily bases, even if they don’t change it a lot.
With microservices, they have to focus on a specific component to build a dedicated feature and do it very well. This component is part of the larger system. It’s probably no surprise that the entire development department should support the new movement. Perhaps there are a few pioneers in the organization which need to convince the skeptical colleagues. Besides this, the management might have specific expectations about the perceived benefits, development teams need to deal with this as well.
Stop feeding the monster
A catchy title but hard to practice if you work on a monolith for quite some time. Some organizations realize their monster is getting bigger every day. One approach to start breaking it down is not to add any more features to it. The more code you add to it, the more you contribute to the problem. Whatever a business manager or Product Owner promises to a stakeholder, stop adding stuff to the monolith. I admit, this is really hard, since you might need to convince your Product Owner and in turn, they need to convince the teams’ stakeholders. This is not an easy task since it’s a bit abstract. The results are not yet (guaranteed).
As soon as you do get the mandate, development teams and the management should decide it’s time to create a new (isolated) service for every new feature. It prevents more technical dept and slowly the monolithic monster becomes less important since it’s losing its functions. Using this approach also helps development teams to politely break resistance from the skeptical people in the organization. Be sure to show results regularly to keep the stakeholders engaged.
Don’t rely back on it anymore
Adding to the previous item, the newly created microservices should not rely too much on the monolith anymore. This means they should not depend on it for example to store their data or depend on an interface that uses legacy technology. Furthermore, it frees the way to switch off these specific features of the monolith. Removing an open endpoint in the monolith also reduced the attack surface, so this also improves security.
Separate data stores
One of the key characteristics of a microservice is to have its own data source. Segregate each data storage per microservice. The Command and Query Responsibility Segregation (CQRS) architecture pattern can help you here. Failing to do this results in a big mess since having multiple microservices talk to the same data storage is even worse than having a big monolith interact with it. Data corruption is hard (if not impossible) to solve. Considering data as your most valuable asset, the results for your organization can be catastrophic. So this should be one of your first steps in the migration.
Edge services in a monolith are the application components that are not so crucial to the application and which are easy to decouple. Start splitting those first.
Using this approach they can also practice to write down operational requirements, which are needed to operate microservices in a proper way. Early feedback about this CI/CD process is important since the teams need to be confident they can take up the more difficult work. Typically this includes the features which are deeply embedded in the monolith.
It’s not just code
One of the next steps is to look at features and components which change very often and which represent a big deal for the business. In order to do this, you need to carefully analyze the code commit patterns. Only looking at code commits is not enough since a component might have undergone a lot of commits to fix a difficult bug or a number of tests. This process is called “social code analysis” and a tool that can help you is CodeSense. Besides this, you need to map these to the product roadmap and talk to your business department to confirm these are actually the most valuable features. Together with them, you capture the technical aspects as well as how relevant these are for the revenue.
Every time you analyze a component to take out you need to carefully balance between the effort which it takes to decouple it and the actual business value. If a component with frequent changes is delivered in a smooth fashion and developers are very familiar with it, it makes less sense to decouple it compared to a component that really constrains the deployment process and which frustrates the changes or other components.
Rewrite or reuse?
Often in the process, you need to decide to reuse existing code or to completely rewrite it.
Consider your new microservice running in the cloud. This requires a completely different deployment model compared to the old situation in which it was part of the monolith running on a VM in your on-premises data-center. From this perspective, reuse would make less sense since the differences are too big. Instead, you should rewrite the new component based on the functional aspects.
It is very likely that developers who work on a project for so long like “their baby” too much and have difficulties giving it away. They might have worked long hours to complete a specific piece of software so they are attached to it very much. This can be an obstacle when deciding on reuse or rewrite. To overcome this: it might help to define organizational guidelines to decide when to choose between reuse and rewrite components.
As a rule of thumb the following arguments can help you:
- Rewrite if the (expected) costs are less than to reuse. Count the number of story points for all the needed user stories.
- Don’t reuse legacy code that cannot be used in your target environment (e.g. don’t reuse boilerplate code like configuration scripts of the environment which changes completely).
- Don’t reuse code that has a lot of (security) issues and code smell. The value to reuse is very low and it creates more risks that require attention.
Rewriting code brings another advantage instead of reusing it: development teams have the opportunity to talk to the business (again) to get the real customer value, they can do suggestions to improve it and try out new technologies to gain knowledge about it.
Every microservice requires a single team to maintain it. This greatly reduces the (communication) overhead to streamline (conflicting) changing between different teams. A popular phrase in DevOps is: “You build it, you run it”, it helps to identify who is the real owner and who is responsible for what.
For example, if an application has different teams being responsible for application components and the infrastructure components, consumers face a difficult time identifying the right team in case of a (serious) error. This creates more risk for an organization since it might lead to unexpected downtime or security vulnerabilities are not patched in a timely manner.
CI/CD pipelines and early feedback
It would be no surprise that every microservice should have it’s own life-cycle. With every code commit, you need to be able to run your checks to see if the change is OK. Proper unit tests are a very important first step. In addition to that, you need to be able to deploy without trouble.
Early feedback is important to validate changes. The risk of breaking the entire application decreases since an error in the microservice does not have an effect on the monolith. Having your CI/CD pipelines ready gives you the opportunity to quickly push new versions to further improve. More valuable feedback helps you to speed up and take the next component out. Stakeholder satisfaction will increase. As they might spread the word, your team is valued and you can get more done in less time so the organization really benefits.
As explored in this article, strangling the monolith is not that easy. It provided tips and guidelines to start your journey. Since the list is never complete and also differs per application and organization, there is much more to explore. I hope this gives you a good start towards micro-services.