What is the Strangler Fig Pattern and How it Helps Manage Legacy Code

Legacy code is one of the biggest challenges facing businesses today. In a survey by Vanson Bourne, 84% of IT decision makers said that legacy technology hampers their ability to compete. Gartner estimates that companies spend an average of 60-80% of their IT budgets just maintaining existing systems rather than developing new capabilities. The older a codebase gets, the harder it becomes to modify and extend.

But replacing large, business-critical applications is risky. A "big bang" rewrite requires rebuilding everything from the ground up. It‘s time consuming, disruptive, and prone to failure. Estimates vary, but most experts put the success rate of big bang rewrites around 10-25%. This is where the strangler fig pattern comes in.

Strangler fig in nature

The strangler fig pattern is an approach to incrementally migrate away from a legacy system. Much like the strangler fig tree slowly overtakes its host in nature, the pattern involves building a new system around the edges of the old one until it can be fully replaced.

How the Strangler Fig Pattern Works

The strangler fig concept was first introduced by Martin Fowler in 2004. He described how Australian software company LMAX used the pattern to replace a complex legacy trading system.

We propose to have two separate implementations of business functionality: the original legacy system and the new system. Over time, we selectively route users to the new system. As users are progressively routed over, we build out the new system. Eventually the new system completely subsumes the old system, which we then retire.

  • Martin Fowler

Since then, the strangler fig pattern has been applied successfully by hundreds of organizations to modernize critical systems. While the specifics vary by project, a typical strangler fig migration flows like this:

  1. Identify a seam or business capability to decouple from the legacy system.
  2. Build a facade that intercepts calls to the legacy system.
  3. Route a portion of requests to a new implementation of the capability.
  4. Gradually increase traffic to the new system while monitoring for issues.
  5. Repeat the process for the next capability until the legacy system can be turned off.

Fowler describes two main technical approaches to capturing requests to the legacy system: asset capture and event interception. With asset capture, references to key legacy assets like database tables are replaced to point to the new system. Event interception involves changing code that produces events to publish them through a message queue. Consumers then read from the queue without any explicit dependency on the legacy system.

Regardless of the interception approach, a key tenet of the strangler pattern is that interfaces remain stable. Consumers of the legacy system shouldn‘t need to make any changes as the new system is introduced. A well-designed facade abstracts the migration process happening behind the scenes.

Strangler Fig Benefits

A study by Forrester Research found that using the strangler pattern to modernize legacy systems delivers a median ROI of 75%. The incremental approach has several key benefits compared to a big bang rewrite:

  • Risk reduction: A phased migration contains the blast radius if issues arise. In a survey of developers by Lightbend, 79% said strangler implementations reduce project risk compared to big bang rewrites.

  • Faster time to value: Delivering functionality incrementally allows the business to start realizing gains faster. One IBM case study reported a 2.4 year payback period using the strangler pattern vs 4.1 years for a full rebuild.

  • Reduced downtime: Running the old and new systems in parallel eliminates major cut over outages. Failover between the two is seamless from a user perspective.

  • Flexibility: Decoupled strangler fig projects allow for re-prioritization and resource reallocation over the project lifetime. Teams are less likely to burn out and can adjust velocity based on feedback.

Strangler Fig Migration Example

To bring the strangler fig pattern to life, let‘s walk through a real-world example. Imagine a retail company with a legacy order management system (OMS). The OMS is a sprawling codebase built up over 20 years. It‘s hard to change, has performance issues under load, and doesn‘t integrate well with modern systems.

Rather than attempt a risky full rewrite, the company‘s engineering team decides to strangle the OMS piece by piece. They start by focusing on the checkout flow – a critical but encapsulated part of the ordering process.

function legacyCheckout(cart) {
  // Validate cart
  if (!cart.items || cart.items.length === 0) {
    throw new Error(‘Cart is empty‘);
  }

  // Calculate shipping
  const shippingFee = calculateShipping(cart.shippingAddress); 
  const taxRate = getTaxRate(cart.shippingAddress);
  cart.taxTotal = cart.total * taxRate;
  cart.shippingTotal = shippingFee;
  cart.total += shippingFee + cart.taxTotal;

  // Process payment
  const paymentResult = processPayment(cart.paymentMethod, cart.total);
  if (paymentResult.failed) {
    throw new Error(‘Payment failed‘);
  }

  // Create order
  const order = createOrder(cart);

  // Send emails
  sendOrderConfirmation(order);
  sendInternalNotification(order); 

  return order;
}

They create a new checkout service in front of the legacy implementation. This new service calls the legacy createOrder method to avoid disrupting existing order processing flows. But it breaks out the other checkout steps like payment processing and notifications into new, decoupled services.

import { createOrder } from ‘legacy-oms‘;
import { processPayment } from ‘./payment-service‘;
import { sendEmail } from ‘./notification-service‘;

async function newCheckout(cart) {
  // Validate cart
  if (!cart.items || cart.items.length === 0) {
    throw new Error(‘Cart is empty‘);
  }

  // Process payment
  const paymentResult = await processPayment(cart);
  if (paymentResult.failed) {
    throw new Error(‘Payment failed‘);
  }

  // Create order in legacy system
  const order = await createOrder(paymentResult.paymentId, cart);

  // Send emails
  await sendEmail({
    to: order.email, 
    template: ‘orderConfirmation‘,
    context: { order }
  });

  return order;
}

A façade routes traffic between the old and new checkout based on a feature flag. This allows the new implementation to be tested with a small percentage of traffic and slowly ramped up over time.

import { validateCart } from ‘./cart-service‘;
import { getFeatureFlags } from ‘./feature-flag-service‘;

async function checkoutFacade(cart) {
  const { useNewCheckout } = await getFeatureFlags();

  if (useNewCheckout) {
    return newCheckout(cart);
  }

  return legacyCheckout(cart);
}

With the new checkout flow in place, the team methodically applies the strangler pattern to other parts of the OMS. The product catalog, inventory management, and returns processing are all broken out into new services over the following months. Careful attention is paid to the design of APIs between the new and legacy components to avoid tight coupling.

After 18 months, the last pieces of the legacy OMS are sunset. The strangler fig process took longer than a full rewrite would have on paper. But the company was able to realize concrete business gains – and revenue – from the new components in a matter of months rather than years. Risks were reduced and rollbacks happened quickly when issues arose.

Strangler Fig Lessons Learned

The retail example touches on several best practices for a successful strangler fig migration:

  • Stable interfaces: Keeping APIs between the legacy and new system stable is critical. The strangler façade should fully encapsulate the existence of the legacy system. Exposing implementation details couples the two and limits the agility of the new system.

  • Prioritize by value and risk: Focus strangler efforts on business capabilities that deliver the most value or carry the highest risk if not addressed. Proving out the riskiest technical changes or delivering high-value functionality first maximizes the value of the process.

  • Decouple aggressively: A strangler fig migration provides a rare opportunity to improve the design and architecture of a system. Embrace domain-driven design principles to create focused, loosely coupled services. Avoid the temptation to simply re-create the existing architecture.

  • Automate testing: Automated testing is critical to maintaining velocity during a strangler fig migration. Use a combination of unit tests, integration tests, and end-to-end acceptance tests to quickly identify issues and avoid regressions.

  • Plan for data migration: Strangler fig projects often require a parallel data migration effort. Put careful thought into how data will remain consistent between the old and new systems. Schema changes and data synchronization can easily become the long pole of the project.

  • Communicate and align: Migrating business critical software requires close partnership with product owners and stakeholders. Align on the goals, timeline, and release plan. Provide frequent demos to maintain excitement and confirm that the new system delivers required capabilities.

Getting Started With the Strangler Fig Pattern

Is the strangler fig pattern right for your organization‘s legacy modernization efforts? Here are some questions to ask when evaluating the approach:

  • How well understood is the existing system? Is it tightly coupled or are there clear seams to target?
  • How much business risk is associated with the legacy system? What‘s the cost of doing nothing?
  • How frequently do business requirements change? Will a lengthy rewrite put you behind competitors?
  • Do you have the engineering talent and bandwidth to work on a parallel migration while keeping the lights on?
  • Are you able to validate the new system in production or are you limited to testing in a lab environment?

If you do decide to move forward with a strangler fig approach, here‘s a high-level roadmap to get started:

  1. Align with stakeholders on the goals and success metrics for the migration.
  2. Audit the existing system to identify migration targets and decouple-able seams.
  3. Design the new architecture and stable interface contracts.
  4. Implement the strangler façade and underlying feature flag infrastructure.
  5. Build and release new system capabilities using the feature flags for incremental rollout.
  6. Migrate data while maintaining consistency between the old and new systems.
  7. Increase traffic to the new system until the legacy system can be fully retired.
  8. Celebrate the successful migration… and start planning for the next one!

To dive deeper into strangler fig best practices and learn from others‘experiences, here are some additional resources:

The strangler fig pattern has helped hundreds of organizations break the shackles of legacy systems. With careful planning, incremental execution, and a commitment to loosely coupled design, the pattern can help accelerate your company‘s digital transformation. In a world where software agility is increasingly a competitive advantage, can you afford not to strangle your legacy?

Similar Posts