Any organization that is not starting from scratch has existing IT systems and infrastructure in place. In many cases, this legacy has issues keeping up with today’s demand for how quickly organizations need to be able to adapt and change. One common pattern in larger organizations is “monolithic applications” which often have been around for quite a while and are highly integrated and complex IT systems.
Since these monolithic systems tend to be calcified to a certain extent, they often reflect (and have been painstakingly optimized) the way an organization works currently, but they often also are hard to change when the organization wants or needs to make changes to what it does and how it does it. Microservices often are mentioned as a way to avoid some disadvantages of monolithic approaches, but since many organizations already have monolithic applications around, how can you transition from the old to the new?
One of the fundamental aspects of microservices is that they are self-contained, which means that they have no hidden dependencies other than the APIs that they are consuming. They also are not used in any way other than through their API. This means that there are fewer (or at least better managed) dependencies and therefore it becomes easier to change (and deploy) microservices. Of course, this still needs careful API lifecycle management to avoid breaking API changes and to manage them when they do occur. But microservices are a good starting point to manage your code in a more agile way than in a highly integrated monolithic application.
One popular way to migrate from monolithic applications to microservices is the strangler pattern. The name refers to strangler vines that grow around trees, gradually building up a solid structure that eventually is able to completely replace the tree that they started growing around. The strangler pattern for microservices means to gradually and strategically build a “mesh” of microservices around an existing monolith, replacing certain functions as needed, and over time potentially replacing the monolithic application entirely.
The main advantage of the strangler pattern is its iterative approach: Instead of a risky big bang of replacing the entire monolithic application, it is strategically replaced in functional increments. For this to work, functions of the monolithic application have to be identified, designed as APIs, and consumption must switch to the exposed API. With this in place, a new implementation can now take over without the API consumer even noticing. This means that the function underlying the API can now be moved to a microservice and one part of the monolithic application can be retired. This process can be repeated as needed and provides a good blueprint for how over time, the monolithic application will be replaced with a set of microservice implementations.
This process can be broken down into a number of steps. Not all of them are equally easy (or easy at all: cleanly delineating business components always is a challenging task), but working through this list will help to better envision and plan the process:
- Motivate: Why are you doing all of this? This has to be clear so that you know what to identify as capabilities that you will replace.
- Identify: Find those capabilities that are causing the greatest pain. One example could be a capability that scales poorly and slows down important business processes.
- Delineate: Figure out how the area identified above can be delineated in your code as a cleanly separate component.
- Analyze: In most cases, the code will depend on a state somewhere in the monolithic application, such as a database. Find all dependencies of your component with this kind of shared state.
- Expose: Expose the capability as an API that is designed with the consumer in mind and does not expose any of the implementation details.
- Manage: Make the API available through a management component such as an API gateway (for event-based APIs this would be the event broker) and make it consumable through this management component.
- Re-Implement: Build a new and self-contained component that rectifies the problems that you have with the original implementation and is designed and built for change and easy deployment.
- Imitate: Expose the new implementation through the same API that is used by the monolithic application.
- Replicate: Make sure to accurately replicate state so that the new implementation can serve as the replacement for the old one. This often involves bulk transfers between databases.
- Motivate: Now it is time to move consumers to the new implementation. There are various schemes for how to do that, some of these are visible to the consumers while others are completely opaque.
- Deprecate: When all traffic is using the new implementation, the API management can be simplified to not expose the monolithic application anymore.
- Retire: If everything goes well, after a transition period you can stop synchronizing state between the implementations and you can retire the code in the monolithic application.
- Repeat: Now that you have successfully migrated one capability, you are gaining experience with the process, and you can start repeating the process by identifying more candidates.
This is a quick summary of the general process. The following video goes into more details and explains additional aspects to be aware of while going through the steps.
If you liked this video, why don’t you check out Erik’s YouTube channel for more “Getting APIs to Work” content?