For companies that depend on legacy applications for critical business processing, modernizing those apps to make them compatible with today’s technologically sophisticated cloud ecosystem is crucial. But because most legacy apps are monolithic, updating them can be a difficult, time-consuming, and risky process.
A monolithic codebase is organized as a single unit that has function implementations and dependencies interwoven throughout. Because a change to one part of the code can generate unexpected side-effects in other parts of the codebase, any update has the potential to cause the app to fail in unpredictable ways.
Yet, if these legacy apps are to continue fulfilling their business-critical missions, they must have the flexibility and adaptability necessary for keeping pace with the ever-evolving requirements of a fast-changing marketplace and technological environment. What’s needed is a means of encapsulating any changes to the legacy code so that only the targeted function is affected.
The Strangler Fig Architecture pattern meets that need. It allows legacy apps to be safely updated by replacing each function with an independent microservice. This enables developers to incrementally modernize specific functions without impacting the operation of other portions of the app.
What is the Strangler Fig Pattern?
Martin Fowler, Chief Scientist at Thoughtworks, coined the term in 2004. He noticed that strangler fig seeds, which germinate in the upper branches of other trees, send down roots that surround and eventually strangle their host tree. In effect, the strangler fig kills the original tree and takes its place.
Fowler saw this as a metaphor for how a large, monolithic software application could be modernized by surrounding it with a new superstructure of microservices that, over time, strangles and replaces the original app. A microservice is a small, self-contained codebase that performs only one task and replaces a single function or service in the legacy app. It can be updated without affecting other parts of the app.
As new microservices are added over time, they take over the functions of the original codebase one by one until the functionality of the legacy app is entirely replaced by microservices. At that point, the original app has been fully “strangled” and can be decommissioned.
Related: Migrating Monolithic Applications to Microservices Architecture
Why the Strangler Fig Pattern is Ideal for Application Modernization
Faced with the daunting prospect of replacing or rewriting their portfolio of legacy apps, some companies settle for simply migrating them, pretty much as-is, to the cloud. But though that approach may yield some benefits, it falls far short of true modernization. That’s because a monolithic codebase in the cloud is still monolithic, and retains all the detrimental characteristics of that architecture.
The Strangler Fig Pattern enables true legacy app modernization by allowing you to replace the functions of the original app one at a time without having to rewrite the entire app all at once. As key functions are re-implemented one by one as microservices, the app continues to function and can be fully transformed without ever going offline.
A key element of the strangler paradigm is the use of an interface layer, called a façade, between the original app and its microservices superstructure. All communications to and from the legacy app go through the façade, which includes feature flags that you can set to dynamically control whether the original code for a function or its microservice replacement is live.
This approach provides some major advantages:
1. Allows incremental updating
If you elect to entirely rewrite a legacy app, you can’t use the new system until the rewrite (and all testing) is complete. The strangler approach allows you to incrementally add features and capabilities without disrupting the operation of the app or taking it offline.
2. Enables quicker modernization
As each new microservice is added, the benefits it provides, such as increased adaptability, flexibility, scalability, and performance, take effect immediately. As IBM notes,
The great thing about applying this pattern is that it creates incremental value in a much faster timeframe than if you tried a “big bang” migration in which you update all the code of your application before you release any of the new functionality.
3. Minimizes risk
Any attempt to replace or upgrade a large monolithic app all at once will almost certainly introduce new bugs that can cause significant downtime once you bring the new codebase online. But, as Bob Reselman of Red Hat explains,
“Small failures are easier to remedy than large ones, hence the essential benefit of the Strangler pattern.”
Because the strangler approach incorporates changes in small steps, with each new microservice being thoroughly tested before going live with the app, downtime due to new bugs can be almost totally eliminated.
4. Allows you to choose the pace of modernization
Since the app is never taken offline, you can implement the modernization project at a pace that’s comfortable for your team (and budget).
5. Allows easy and seamless rollbacks
Rolling back a change that isn’t working correctly is easy. Each new microservice deployment can be quickly and cleanly reversed simply by setting feature flags appropriately.
6. Eliminates the need to maintain two separate codebases
New functions are implemented as microservices that surround the legacy codebase; the original app is never changed. Since any needed changes (including those that correct bugs in the legacy code) are made only to the microservices superstructure, the original codebase need not be separately maintained.
7. Enhances QA
Because microservices can be run in parallel with the original code for QA purposes, each change can be comprehensively tested in the app’s production environment before it goes live with the app.
How Refactoring With Strangler Fig Helps You Avoid Destructive Coding Anti-Patterns
As we’ve seen, the Strangler Fig Pattern provides an ideal template for modernizing legacy apps. However, some other widely used software design patterns yield far more negative results. These are called, appropriately, anti-patterns. Martin Fowler notes that the term was coined in 1995 by programmer Andrew Koenig, who described it this way:
“An antipattern is just like a pattern, except that instead of a solution it gives something that looks superficially like a solution but isn’t one.”
According to Fowler, anti-patterns are extraordinarily dangerous because they initially fool developers into thinking they are appropriate solutions to common software coding problems, only to reveal their detrimental consequences later when the damage has been done. As software engineer Kealan Parr declares:
“In software, anti-pattern is a term that describes how NOT to solve recurring problems in your code. Anti-patterns are considered bad software design… They generally also add “technical debt” – which is code you have to come back and fix properly later.”
Parr lists some of the more common anti-patterns. They include:
Spaghetti Code
This anti-pattern is often encountered in monolithic legacy apps. The term describes a codebase that has little or no structure. There’s no modularization, and function implementations and dependencies are intermingled throughout the code, just like strands of spaghetti on a plate. As a result, the logical flow of the application is extremely difficult to understand. Parr calls it a maintenance nightmare:
“You will constantly break things, not understand the scope of your changes, or give any accurate estimates for your work as it’s impossible to foresee the countless issues that crop up when doing such archaeology/guesswork.”
Because of these characteristics, updating or adding features to spaghetti code is an extraordinarily difficult and risky process.
Golden Hammer
This anti-pattern derives its name from a quote attributed to Abraham Maslow:
“I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.”
That’s a human tendency to which software developers are as prone as anyone else. When they have a high level of competence with, love for, or comfort with particular coding tools, languages, or architectures, they naturally seek to apply them across the board, even in situations where they are not the best options. The result can be highly detrimental—as Parr says, “Your whole program could end up taking a serious performance hit because you are trying to ram a square into a circle shape.”
Boat Anchor
Boat anchors do nothing but retard the progress of the vessel to which they are attached. And that’s exactly what the boat anchor anti-pattern does with software. The term describes code modules that developers insert into the codebase to implement functions not currently needed or used. They do so because they think those functions might be needed later.
This too, says Parr, is a maintenance nightmare. Developers who are new to the codebase, or who have not worked with it for some time, will have a hard time identifying boat anchor modules and figuring out whether they impact the logical flow of the program or are entirely superfluous. There’s a real possibility of your developers spending significant amounts of time and effort on understanding and debugging modules that literally do nothing.
This term describes code that, unlike boat anchors, implements functions that are not only used in the application but which may be called frequently from many different places in the codebase.
The problem is that it’s not clear what this code is doing or why it’s needed. Perhaps it had an important function at some point, but now the issues it was created to solve no longer exist. On the other hand, it could be crucial for handling infrequent edge or boundary conditions that current developers haven’t yet run into but eventually will. Because you can’t be sure why it’s there, you don’t dare to remove it. So, it remains in the codebase as a time-waster and generator of confusion for the developers who have to deal with it.
Proliferation of Code
This anti-pattern occurs when there are objects in the code that seem to exist only to invoke other more strategic objects. These are essentially useless “middleman” objects that provide no additional value, but only add an unnecessary level of abstraction, and therefore confusion, to the code. Such objects should be bypassed and removed to make the code more easily understandable.
God Object
This is sometimes called the “Swiss Army Knife” anti-pattern. It describes objects that are accessed by many other objects in the codebase for a multitude of different and often unrelated purposes. Such objects are problematic because they violate the Single Responsibility principle of coding, which says that every class, module, or function should do only one thing. According to software architect Thanh Le, they are “hard to unit test, debug and document” and can be a “maintenance nightmare.”
Strangler Fig to the Rescue!
Refactoring a legacy app according to the Strangler Fig Pattern will remove anti-patterns such as these from the codebase almost automatically. For example, Strangler Fig refactoring eliminates spaghetti code by re-implementing legacy app functions as a set of independent, single-task microservices that can be easily understood, maintained, and upgraded.
Similarly, code that does too much is replaced by individual microservices with precisely specified, single-purpose functionality, while unneeded modules are not re-implemented at all. And Golden Hammer technology choices can be eliminated by implementing new microservices using a carefully chosen modern technology stack.
Best Practices to Implement the Strangler Fig Pattern
How can you maximize the benefits of the strangler fig paradigm in modernizing your legacy apps? Here are some best practices:
1. Automate the process using an AI-based modernization platform
As industry insider Oliver White has said,
“Large monolithic applications need an automated, data-driven way to identify potential service boundaries.”
Manual analysis of a monolithic codebase with millions of lines of code is a time-consuming, error-prone process. An automated, AI-based analysis platform can perform that task quickly, comprehensively, and at scale. Using static and dynamic analyses, it can assess the monolithic codebase for technical debt, complexity, and risk; reveal functional flows and dependencies; identify service domain boundaries; and quantify the amount of effort that will be needed to refactor the app.
That information will allow you to determine:
- the negative impact of your legacy apps’ technical debt on your ability to innovate
- the ROI that can be realized from modernizing some or all of your legacy apps
- which applications should be modernized and in what order
- which legacy app services should be re-implemented as microservices and which should not
- the functional scope of each microservice
- which functions are so similar or overlapping that they can be consolidated into a single microservice
Once the analysis phase is complete, a state-of-the-art modernization platform will be able to substantially automate the process of refactoring the monolithic code into microservices.
2. Pick the right starting point
For most companies, it’s not feasible—nor desirable—to modernize all their legacy apps at once. Instead, it’s best to start with those apps that have the greatest business value and which also have a high degree of technical debt. Then, for each app choose functions that have the highest impact on your business operations as the first to be re-implemented as microservices.
3. Pick the right ending point
It’s natural to want to replace all your legacy apps with microservices. But the costs of refactoring an entire legacy suite may exceed the benefits. In such cases, it might be best to continue using the original app for specific functions that are isolated, stable, and don’t require upgrading, while re-implementing as microservices any functions that must be easily upgradeable, or that interact directly with other systems or resources.
4. Follow an incremental, step-by-step process
The Strangler Fig Pattern provides its greatest benefits when it is applied incrementally, one microservice at a time. Avoid trying to modernize entire apps all at once. As one research paper succinctly advises:
“Start small and gradually evolve the system (baby steps).”
5. Implement new functionality only in microservices
When you begin the modernization process, you should freeze the legacy codebase and implement any new functionalities only through microservices. If you continue to make updates to the original app, you create two simultaneously evolving codebases, both of which must be supported, tested, and synchronized.
Related: Simplify Refactoring Monoliths to Microservices with AWS and vFunction
How AWS Migration Hub Refactor Spaces and vFunction Work Together
As an AWS Partner, vFunction provides an automated, AI-driven modernization platform that closely integrates with AWS Migration Hub Refactor Spaces to enable developers to quickly and safely transform complex monolithic Java applications into microservices and deploy them into AWS environments.
Refactor Spaces establishes, maintains, and manages the modernization environment, and orchestrates AWS services across accounts to facilitate the refactoring of legacy apps from monoliths to microservices. Refactor Spaces implements the Strangler Fig Pattern for the target application and allows developers to easily manage communication between services throughout the environment.
Developers begin the refactoring process by using vFunction to generate an automated, AI-based analysis that quantifies the complexity of monolithic legacy apps. Using both static and dynamic analyses, vFunction provides the detailed information regarding technical debt, complexity, and risk that’s required for developing a comprehensive refactoring plan that prioritizes which apps and services will be converted and in what order.
vFunction then automatically decomposes the monolithic apps into microservices. Using sophisticated, AI-driven static analysis, the vFunction platform analyzes architectural flows, classes, usage, memory, and resources to detect and expose critical business domain functions buried in the code and untangle complex dependency relationships.
See vFunction For Yourself
The vFunction platform is unique in its ability to make refactoring monolithic legacy apps into microservices as quick, easy, painless, and safe as possible. It easily handles codebases with tens of millions of lines of code, and can accelerate the modernization process by at least a factor of 15. If you’d like to see for yourself what it can do, please schedule a demo today.