Admittedly, the term "Strangler Pattern" doesn't sound all that great. But it is actually a pattern that can prove to be very useful for a wide variety of use cases.
- Breaking down a monolithic application into newer microservices.
- Migrating existing infrastructure from one platform to another.
- Migrating on-premise applications to cloud providers.
- Moving off a legacy application to a more modern code base.
The strangler pattern at its core is an incremental migration of an application or service one piece at a time. The idea is rather simple, we incrementally build up a new system or architecture over time at the edges of the old system. We avoid adding additional things to the old in favor of the new and over time we upgrade the legacy pieces into the new system.
When to use the Strangler Pattern
Let's imagine for a minute that we have this big monolithic application that is currently running on some older pieces of technology. Maybe it's an old version of .NET, maybe it's an unsupported version Java, or maybe it's just a big behemoth codebase that needs to be broken down.
Either way, it's time to bring this application or service into the 21st century.
In either one of these scenarios, your choices really boil down to three options.
You may just decide to rebuild it from the ground up if it's still needed. This is viable but it can take a long time and it's a big bang operation. Meaning when it's done, we flip the lights on, cross our fingers, and hope we got it right.
You decide to move, upgrade, or refactor the behemoth in one fell swoop. What could go wrong right? This can be very daunting and a huge time drain. Refactoring business logic might introduce more instability. Upgrading could result in downtime if something goes wrong.
The final option is to incrementally improve the situation. Whether it's an upgrade or data center move, doing it incrementally is preferable over any big bang type of operation. Why? Because we can limit the blast radius of any downtime. Furthermore, we can limit the impact of any failures because we can incrementally roll them back if needed.
Option three is what makes the most sense for most situations. It's certainly not universal, and there are times where the other two make the most sense. But for this post let's focus on option three.
Deploying the pattern
The Strangler Pattern allows us to build up a new system at the edges of our old system and incrementally shift traffic over to our new system as things progress.
With that in mind, we can think of our current monolith living in a box.
The box encompasses everything that makes up our legacy application or service. We see that we have a few different pieces of logic, X service, B service, etc. that make up our current monolith. We also see that some of these services call one another, this is important to remember as we start to build our new system.
To get started with the strangler pattern, we are going to put a bouncer at the entrance to our box. This is often called a facade, router, or proxy but bouncer sounds more entertaining, so stay with me.
![Monolith with a facade](monolith-architecture-with-facade.png
The first step of using the strangler pattern is adding this bouncer in front of our monolith. All requests to our monolith now must come through our facade. For now, it is just going to relay the request directly back to the monolith.
Once, we have our bouncer at the door we can start to think about how we want to incrementally break down our monolith. For the sake of simplicity let's imagine that each service in our monolith is a single microservice that could be pulled out.
So which service from the monolith should be moved first? Honestly, the choice is pretty arbitrary, but there are a few things to consider.
- Moving a service that a lot of other services are relying on might be a good way to rip the band-aid off, but it might also introduce additional complexity that you're not prepared for yet.
- Moving a service that is hardly used might be simpler, but it also might mean that it's not really used so maybe there is no point in moving it.
Either one of these choices is valid and each comes with their own tradeoffs. When selecting your first service to move I suggest thinking about how clear the boundaries are of that service. If they are very clearcut, choose that one. If they are a bit fuzzy, maybe push that one back a bit. The reason is that things that are not clearcut are typically entangled with the monolith which means the complexity of your move could go up quite a bit.
But, we also don't have to start with existing services. Remember that we said we want to stay at the edges of our old system so we should avoid adding new things to the old system. This means that anything new should not go into our monolith, but rather it should go into our new boundary.
Simple enough right? For anything new, we can create a new microservice or combine it with existing microservices if it makes sense. We use the router to direct requests to our monolith or new service.
But, now let's look at moving an existing service. After all the point of the pattern is to chip away at our monolith so that we can eventually get rid of it. I'm going to start with the E-service since it is only called by one other service, G, inside of the monolith.
Now we see something interesting right?
We pulled out the E-service into its own microservice. It gets traffic routed to it via the bouncer at the door, but how does the G-service communicate with it? Notice that it doesn't call it directly, it now calls it as any normal request would.
Is there anything wrong with G calling the E service directly? Not necessarily, but it does create coupling that you likely want to avoid. By routing the traffic through our bouncer we make it so that E can be independently developed and G doesn't need to know any implementation details of E.
If we continue down this path we can incrementally move each component inside of our monolith into their own services. Maybe some of those services could be combined into one service if the service boundary makes sense. Maybe while we are moving services we realize we don't need one or two, that's totally fine just make sure the logic those currently handle is reflected elsewhere if needed.
The goal with the Strangler Pattern is to incrementally transform our old system into a shiny new system. Over time we will upgrade, replace, and delete services until we reach the point where we can shut off the monolith all together.
Will this happen overnight? No. Will this happen in a week? Likely not.
OK, but when will it be done? That all depends on your existing application and architecture. That's the beauty of the pattern, you can decide how fast or slow this incremental upgrade happens.
In this post we focused on the core principle of the Strangler Pattern, incrementally building up a new system at the edges of an old system. By building up our new service at the boundary of our old service we are able to incrementally cut off our old system, or strangle it. It is a very useful pattern to keep in the back of your mind anytime you are dealing with some amount of technical debt.
Maybe you just need to replace an old service in your application that is no longer needed? This pattern can help with that. Or maybe you need to replace your entire monolith as we discussed here, yup it can help with that as well.
By incrementally deprecating an old system we give ourselves the time to build up our new system. We also avoid the "big bang" release where we cut all of our users over to our new system. By doing these processes over time we can incrementally move pieces without users knowing any different.
A few references
Here are some posts from others on the topic of the Strangler Pattern that you might find helpful.