The Strangler Pattern enables a gradual replacement of legacy code with microservices, helping reduce complexity in moving from monoliths
When someone says “technical debt,” what is the first thing that comes to your mind? Is it the accumulation of code in an outdated system that’s seen changes from tens, or even hundreds, of developers over the years? Is it a codebase with poorly written methods following convoluted logic and hard-to-follow patches for production issues? It’s both, really.
Technical debt is bound to accumulate at every company, whether it’s a mammoth enterprise organization or a young, bright-eyed startup. There will always be tasks that we could (and probably should) do to improve software quality. Anytime we choose not to do them, whether to save time or money or because we just have to deploy something, we contribute to our technical debt.
Before you know it, you might find yourself preparing to transition a massive, complex monolith application to microservices and realize that you have no idea where to start and there’s no one left at the company that knows how the foundational code of the software works.
That’s when the Strangler Pattern might come in handy.
The Strangler Pattern and Legacy Code
So, what is the Strangler Pattern, anyway?
While on a trip to Australia in 2004, Martin Fowler witnessed a peculiar tree and came to a new understanding of how outdated applications can be updated:
“One of the natural wonders of this area [Australia] is the huge strangler vines. They seed in the upper branches of a fig tree and gradually work their way down the tree until they root in the soil. Over many years they grow into fantastic and beautiful shapes, meanwhile strangling and killing the tree that was their host.”
Similar to the way fresh and new vines replace the original base of the tree, new code is written around the edges of the outdated system in such a way that the old system is eventually “strangled” and replaced by the new one.
Legacy code is more difficult to maintain, and it’s time to understand how we can dig our way out of the hole that our previous selves dug us into. Replacing code that’s not working as it should is a difficult—and sometimes dangerous—task that could lead to the application behaving differently—or, worse, breaking entirely. We need to replace the old with the new, but we need to do it the smart way.
The good news is that it’s possible, through a graduated process in which we rewrite the old code, run the new code in parallel, test the environment and make sure everything is working as it should. Or, in other words: Microservices to the rescue?
With microservices, different sections of the application are separated and encapsulated, each with its own logic, data, structure and so on, and perform a specified function.
This buzzword has been around for a while, and companies are adopting and adapting the new elements that come with it. It’s no wonder, considering it brings many benefits into our workflow and build methods, including shorter development times and increased flexibility.
Unlike a monolithic architecture, microservices make it easier for us to understand, develop and test individual elements of the main product, and it helps make each part much more resilient to architecture erosion.
Microservices allow us to redesign and rewrite critical elements within a codebase in parallel to the old methods and code that’s currently in use. In return, it’s easier to control the gradual transition from the old to the new, ensuring that the application is still up and running as expected.
There are a few essential benefits microservices introduce into our workflow. The first one is that it prevents our monolith structure from becoming even more unmanageable. The second benefit is that it helps dev and ops teams move forward with certain elements within the application or product faster, without relying on other teams or areas to finish or deploy their changes.
Microservices can also improve the build-test-deploy-monitor cycle. They allow teams to complete this cycle for individual elements within the application much faster, without having to wait for testing or deploying other features. You can iterate faster.
However, it’s not all sunshine and rainbows in the microservice architecture, and there are a few downsides that come when switching to this method. One of the most critical elements we need to remember when moving toward a microservice architecture is:
With Distributed Systems Comes Distributed Everything
There are quite a few problems we’re likely to face both during and after the transition to microservices. One of the biggest is monitoring. We now have an entire application that lives on “containerized” services. Each service can use its own language and technology, can live on a different machine or can have a different version control, and it’s up to our teams to monitor and identify issues as they occur.
Luckily, application performance management systems (APMs) will still tell us when performance drops and where, and logs will still tell us about issues we foresaw. Everything beyond that gets much more complicated, though. Not only do we need to understand in which machine, container or server this issue happened, but we also need to be able to understand the path the code took to get there and how other services may be involved.
While it might sound like a knock against microservices, it doesn’t mean that we shouldn’t make the move. It just means that we need to be aware of these challenges and that we need to consider new capabilities we’ll require when setting up the monitoring for our environment.
The industry’s move toward microservices is well underway, but teams with a lot of technical debt or massive legacy codebases may find themselves unsure of where or how to start. Adopting the Strangler Pattern as a design framework can help during the transition and continue to help keep your application up, running and optimized as your software grows and advances.
Whether you’re dealing with legacy code or beginning the process of “strangling” your old system or running a newly containerized application on Kubernetes, you need to understand the quality and reliability of your system. When something goes wrong, understanding the path the system took there—and why—is critical.