loading...
Cover image for How to Breakthrough the Old Monolith Using the Strangler Pattern

How to Breakthrough the Old Monolith Using the Strangler Pattern

kylegalbraith profile image Kyle Galbraith Originally published at blog.kylegalbraith.com ・6 min read

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.

  1. 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.

  2. 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.

  3. 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.

Current monolith architecture

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

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.

New microservice

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.

Removing an existing service

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.

Conclusion

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.

Are you hungry to learn even more about Amazon Web Services?

If you are looking to begin your AWS journey but feel lost on where to start, consider checking out my course. We focus on hosting, securing, and deploying static websites on AWS. Allowing us to learn over 6 different AWS services as we are using them. After you have mastered the basics there we can then dive into two bonus chapters to cover more advanced topics like Infrastructure as Code and Continuous Deployment.

Posted on by:

kylegalbraith profile

Kyle Galbraith

@kylegalbraith

Programmer by day and author by night. I am passionate about all things development related, but especially Amazon Web Services. I recently created a course about learning AWS by using it.

Discussion

markdown guide
 

This is super useful, recently I've been thinking about how one might break a monolith into microservices and this answers it perfectly!

I do have two questions:

  1. Once you've moved all bits of the monolith into microservices, would you remove the bouncer and instead modify nginx/apache/etc to direct requests to relevant microservices instead?

  2. Or could you instead change the bouncer into a microservice that receives requests from nginx/apache/etc and instead of redirecting, send a message onto a message bus service like Kafka or RabbitMQ and instead communicate that way?

 

I don't have much experience using microservices per se, but a more general software engineering background, so here's my two cents (also I prefer the term "facade" instead of "bouncer"):

  1. The job of the facade is to present a single interface to the outside world (in this case for compatibility), regardless of the inner workings. If you were to remove the facade and instead use your server config to glue everything together, wouldn't your server config become the facade? Now the question is: how would you like to manage your facade or interface? In the server config, or in the code next to (but largely independent from) the code of the other components? I'd say the latter.
  2. I'm not familiar enough with Kafka or RabbitMQ and what they do, but I can say this: if you're using it as a way to communicate between the facade and the individual components, then go ahead. If you want the individual components to communicate with each other as well, then beware of coupling. You might end up with individual components which communicate so much with each other over the bus, instead of through the facade, that they become/remain tightly coupled, and it's still hard to change one component. Remember: the aim of refactoring is that each component has a single responsibility with a single interface (although that might not be feasible within the constraints that you have)

I think this is roughly what Kyle also replied, but written a bit more verbose. I hope this helps :)

 

How would we send messages to other Micro services through the facade? I realise this isn't an implementation blog post but It's not really mentioned.

 

I would keep the bouncer as it allows you to keep that level of decoupling for any future enhancements you want to make.

 

I enjoyed this read! Thank you for your clear, concise writing and excellent use of diagrams. 😁

 

Thank you for the very kind comment Phillip.

 

This is a clear explanation! Thanks!

I've seen the Big Bang fail before (... I initiated it :(, but only once, luckily). And I've seen this Strangler pattern work... for some time.

If you don't aim to finish Strangling your monolith into the New Style, after a few years you'll end up with a half-migrated system.

And then comes a new insight, a better technology, ... you want to reshape your Half-Monolith into. You could apply the same technique again, but you'll have two generations of software to migrate piecemeal.

I would conclude that "The Strangler pattern works for systems up to a certain magnitude".

mikehadlow.blogspot.com/2014/12/th...

 

Excellent point! Often times a monolith if far better than a totally fragmented system where half the pieces are one way and the other half another.

 

Nice writeup. We implemented something similar 5 years ago and called it the Bridges Pattern, which did a redirect to the new system if the config data had the feature marked as "ported". We need more articles written on how to safely refactor legacy code. Most developers won't get to work on greenfield but it is much more sexy and fun to write and read about.

 

Thank you for the very kind comments. I agree that more articles on how to update/refactor legacy systems would be awesome!

 

Not that I didn't enjoy this read -- I totally did. I just wanted to point a few things out because we tend to rename things in our industry and then say it's something radical and new -- especially if it's the exact same pattern as something else, but just applied to a different scale. And, we do this a LOT. So, here's what you described with "The Strangler Pattern:

The Strangler Pattern is a Refactor Pattern known as Branch By Abstraction (BBA, Martin Fowler et al). We use an API Gateway -- which is, more or less, The Mediator Pattern (GoF) applied to a higher scale across an HTTP Web API -- to facilitate stability across time and implementation-space. One can think of the "Bouncer" or "Facade" (Gateway) as the Director from The Mediator Pattern, because, a Director inherently implements The Facade Pattern. That is, it unionizes multiple interfaces (Facade) while also implementing triggers & routing across those submodules which it encapsulates.

So in a nutshell, the TL;DR of it is: when migrating an app, use a Gateway for your BBA pivot.

Having said all that, I know we're still going to pretend that this is some new, radical, hip, unheard of pattern... Ya know, because, "components" are not modules, and "thinking in components" is not SOLID. That is, we'll just relabel it and pretend we're inventors.

Anyway, I still like the article.

 
 

Thanks for sharing this, we are doing that right now and I didn't know that it actually had a name.