The Strangler Fig Pattern: Modernizing Legacy Systems Step-by-Step with Rust
Learn how the Strangler Fig pattern replaces legacy systems step-by-step with microservices, reducing the risks of complex system migrations.
Table of Contents
- What the Strangler Fig Pattern Really Delivers
- Where the New System Boundary Should Be Drawn
- A Transition Architecture with an Expiration Date
- Legacy System Migration in Five Phases
- Why Data Migration Dictates the Pace
- The Strategic Role of Rust Microservices
- Typical Risks and Tactical Countermeasures
- How to Measure Migration Progress
- When Is the Strangler Fig Pattern the Wrong Choice?
- Conclusion: Decommissioning Responsibility, Not Just Code
- FAQ: The Strangler Fig Pattern
- References and Further Reading
Mission-critical legacy systems are rarely just old software. They contain business rules, edge cases, and integrations accumulated over years that keep daily operations running. This is precisely why a complete replacement is so risky: the new system must not only deliver the same scope of functions but also replicate behaviors that are often not fully documented anywhere.
The Strangler Fig pattern distributes this risk into manageable steps. New features and clearly defined functional slices are built alongside the legacy system. A routing layer decides whether a request is processed by the legacy system or by a new service. With each successful transition, the legacy software loses responsibility until it can eventually be decommissioned. This approach is commonly described as legacy software modernization or legacy system migration.
This article demonstrates how the Strangler Fig pattern works, where new system boundaries should be established, and how a step-by-step migration succeeds in five phases. We also highlight the critical role of data migration and explain why high-performance Rust microservices are the ideal choice for modern target architectures.
What the Strangler Fig Pattern Really Delivers
Martin Fowler’s Strangler Fig metaphor describes new software that gradually grows around the perimeter of an existing system. This makes risk, investment, and value manageable in smaller, incremental units.
Technically, the pattern consists of three recurring movements. First, an entry point is created through which requests can be intercepted. Second, a new application or service takes over a clearly defined domain capability. Finally, the legacy path is removed once usage, data, and dependencies have been fully transitioned. This cycle is not performed just once for the entire system, but repeatedly for each functional slice.
Parallel operations of both the new and the legacy system should not be viewed as an unwanted side effect, but as an essential part of risk control. The business can continue to deliver new features while the modernization is ongoing. Each migrated domain capability creates a verifiable intermediate state and can already deliver business value. However, additional transition architecture is introduced temporarily: routing, adapters, data synchronization, and parallel operational structures must be developed, monitored, and eventually decommissioned.
Thus, this article deliberately moves past a generic justification for modernization. The economic warning signs that indicate a legacy problem are already discussed in the article The Hidden Cost of Legacy Systems Most Businesses Overlook. This article focuses specifically on the architecture of the actual transition.
Where the New System Boundary Should Be Drawn
The first boundary should not be drawn along technical layers such as presentation, business logic, and database. Such cuts create highly coupled services that must be modified together for almost any change. A better starting point is a domain capability with its own distinct purpose, such as price calculation, document generation, fraud detection, or inventory reservation. The new service can then handle a complete domain capability instead of merely offloading a technical subtask of the monolith.
Externally, a facade or proxy typically manages the routing. Clients and calling systems continue to use the same entry point while the facade forwards specific paths to either the legacy core or the new services. Internally, an Anti-Corruption Layer (ACL) may also be required. It translates legacy data structures and concepts into the new domain model so that historical conventions do not dictate the architecture of the new service.
The Microsoft description of the Strangler Fig pattern explicitly warns against letting the facade become a bottleneck or a single point of failure. The routing layer therefore requires the same engineering standards as a production service: redundant instances, load testing, timeouts, comprehensive monitoring, and sound configuration management.
A suitable first candidate satisfies as many of the following criteria as possible:
- Functional cohesion: The domain capability has a clearly defined responsibility and a clear owner.
- Interceptable access point: Requests can be routed via URL, message type, tenant, function, or an existing integration point.
- Bounded dependencies: The slice does not require internal state from multiple areas of the monolith on every call.
- Independent data ownership: It is possible to define which data the new service will own long-term.
- Measurable value: Error rates, change cycles, throughput, resource consumption, or security risks can be compared before and after the migration.
A Transition Architecture with an Expiration Date
The architecture during migration is deliberately more complex than the desired end state. Two implementations run in parallel, data is temporarily duplicated, and adapters connect models that are meant to be separated long-term. But these building blocks are very important and highly valuable because they enable incremental switchovers and a controlled rollback path. They only become problematic when a planned transition architecture degrades into an indefinite permanent state.
Therefore, every temporary element must receive an exit criterion at the moment of its introduction. For a routing rule, this might mean that 100 percent of production traffic has run stably through the new service for six weeks. A data synchronization mechanism ends when all downstream systems have switched to the new source and reconciliation reports are completely error-free. An Anti-Corruption Layer is decommissioned once no legacy caller requires the old data model. All of these exit criteria belong directly in the roadmap and the technical backlog.
Ownership must also be clearly defined. During the transition, the new service’s team and the legacy team must share responsibility for the entire business process. Without this shared ownership, critical gaps open up at the boundaries: the new service is celebrated as complete, while hidden dependencies—like legacy batch jobs, admin tools, or reporting streams—are left behind, still quietly relying on the old system.
A highly effective tool is a migration register (often set up as a live dashboard) per domain capability. It tracks the current percentage of traffic routed to the new service, the leading database/source of truth, known callers, rollback path, open dependencies, and decommissioning dates. This ensures that progress is visible not just through closed tickets, but through the actual removal of responsibility from the legacy system.
- Practical Hints for Your Migration Register & Dashboard:
- Keep it Central & Shared: Host the register or dashboard in a highly visible space (like a shared wiki, Confluence, or a live BI dashboard) so both developers and business stakeholders have a single source of truth.
- Automate Status Updates: Whenever possible, use simple CI/CD or configuration script hooks to pull live routing percentages or active feature flags directly into the tracker. This keeps the dashboard accurate without tedious manual updates.
- Link Decommissioning to Triggers: Tie decommissioning dates to concrete, empirical milestones (e.g., “100% traffic running stably for 4 consecutive weeks”). This prevents the old code from running indefinitely.
- Track Callers, Not Just Endpoints: Do not just list the main API; list the exact scheduled CRON jobs, backup tasks, and external webhooks that call the domain, ensuring none are missed before shutdown.
Legacy System Migration in Five Phases
1. Identify Business Goals and Trace Real Usage Paths
At the beginning, there is typically a business goal. Is a particularly error-prone feature in need of stabilization? Is a specific part of the system blocking frequent releases? Are operational costs for a heavily loaded path skyrocketing? This prioritization ensures that the migration does not begin at a technically simple but commercially insignificant point.
Next, real requests, dependencies, and data flows must be traced. Architectural diagrams alone are rarely sufficient. Access logs, distributed traces, query logs, batch jobs, and discussions with the operations team reveal which callers actually exist. At the same time, a baseline is established for latency, error rates, load profiles, support tickets, and modification efforts.
This inventory must also capture non-functional requirements that are barely visible in the codebase: maintenance windows, retention periods, third-party contracts, compliance certifications, and seasonal traffic spikes. They dictate when a path may be safely switched over and how long a rollback capability must remain active. A technically clean boundary is useless if it violates a monthly closing cycle, an audit requirement, or a customer’s service-level agreement.
2. Introduce a Controlled Routing Entry Point
The facade is initially placed in front of the existing system without changing any behaviors. All traffic continues to flow to the legacy system. This seemingly small step verifies whether routing, authentication, headers, sessions, timeouts, and error behavior remain unchanged. Only when the facade itself is proven stable and highly observable should it begin sending production requests to a new service.
Not every migration requires a new, dedicated API gateway. An existing reverse proxy, messaging infrastructure, or an in-app adapter can serve the same purpose. The critical requirement is that the routing map can be unambiguously configured, thoroughly tested, and rapidly rolled back.
3. Build a Complete Domain Capability as an Independent Service
The initial new service should handle a single domain operation end-to-end. Its public interface must follow the required functional contract and should not simply copy internal classes or database tables of the legacy system. Where terms or formats diverge, an Anti-Corruption Layer translates between the legacy and domain models. AWS describes this layer as an adapter that insulates the new domain model from legacy semantics and leaves existing callers unaffected during the transition.
Acceptance criteria must include contract tests against documented examples and against observed legacy system behavior. For deterministic functions, requests can be mirrored/shadowed: the new service computes the result in parallel while the production flow still relies on the legacy response. Any deviations are logged, classified, and resolved or accepted only after domain-level review.
4. Deliberately Shift Data Ownership
A service extraction is only complete once it is clear which system is responsible for the underlying data. A new service that permanently writes directly to the monolithic database may run in an isolated process, but it lacks genuine independence. At the same time, an immediate database separation is often too risky because reports, batch processes, or other legacy functions still need access to those tables.
Therefore, every domain requires a transition plan: initial data import, time-bounded synchronization, data consistency audits, cutover of the leading system (system of record), and the subsequent removal of the old tables or write paths. This sequence must be finalized before production write operations are split.
5. Transition Traffic and Remove the Old Path
The rollout starts with internal or low-risk traffic and is gradually expanded through defined stages. Routing by tenant, region, user group, or percentage limits the blast radius of any failures. For each stage, abort criteria must be established—such as elevated error rates, domain-level deviations, or an exhausted latency budget. Equally important is a thoroughly tested rollback path, which must remain viable as long as the old implementation is kept active.
After a stable observation period, you must do more than just update the routing. Legacy endpoints, batch jobs, direct database queries, configurations, and monitoring rules must be verified as deleted. Otherwise, the legacy system shrinks in architectural diagrams but remains alive in operations. Only the final cleanup completes a migration step and actually reduces overall complexity.
Why Data Migration Dictates the Pace
Code is relatively easy to run in parallel; data is far harder because inconsistent data states immediately cause severe business issues. Therefore, a clear System of Record must be established before any cutover. While copies and read replicas may exist during the transition phase, there must never be two equal owners of the same piece of information.
A common pattern combines an initial data import with Change Data Capture (CDC). Changes from the legacy data store are subsequently streamed as events to the new storage. Tools like Debezium accomplish this by reading transaction logs or replication streams, reducing direct modifications to the legacy codebase. However, this does not eliminate the need for ordering, replayability, and data consistency checks.
Dual writes, where a business function writes to two databases, appear simple on the surface but create a critical failure mode: one write operation may succeed while the second fails, leaving the data split. A more robust approach typically involves a single leading write path, a transactional outbox, or an auditable synchronization pipeline. Regardless of the mechanism, the migration requires reconciliation reporting, idempotent event processing, and a well-defined point beyond which rollbacks require explicit data repatriation.
The Strategic Role of Rust Microservices
The Strangler Fig pattern does not dictate any specific programming language. This language independence is a major operational advantage: by isolating system boundaries behind network protocols (such as HTTP, gRPC, or messaging queues), the pattern naturally enables a polyglot architecture. It provides a low-risk, gradual pathway to introduce modern, high-performance programming languages like Rust into a legacy environment, without requiring an all-at-once rewrite or massive immediate re-skilling of the development team.
Rust is particularly compelling when an extracted service processes high-volume workloads, requires highly predictable resource consumption, or processes untrusted data. However, a service should never be written in Rust simply because the language is modern; the technology choice must directly address the system bottleneck and align with the long-term knowledge available in the team for operations and maintenance.
The broad advantages, limitations, and frameworks of Rust in backend engineering are already explored in our article Rust for Backend Development: Pros, Cons, and Use Cases. For legacy migration, a different characteristic is decisive: the new service can be developed and deployed as an independent artifact with a clearly versioned interface contract. This draws a clean boundary between systems, rather than between different languages within a single process.
This boundary requires explicit policies for timeouts, retries, idempotency, and error responses. Synchronous RPC calls are suitable for immediate results but increase coupling at runtime. Asynchronous events temporally decouple sender and receiver, but demand clear ownership and the handling of duplicate messages. Simply building multiple microservices around a single database does not result in a modern architecture—it merely creates a distributed monolith.
Typical Risks and Tactical Countermeasures
- The facade becomes a permanent solution: Document an owner, exit criteria, and a planned removal date for every single routing entry.
- The new model inherits legacy terms and technical debt: Build a deliberate Anti-Corruption Layer that translates between the models and remains strictly service-centric.
- Data belongs to two systems simultaneously: Define a single leading write path per domain for every phase of the migration; keep replicas synchronized, audited, and strictly temporary.
- Too many microservices are built too early: Focus on extracting coarse-grained domain capabilities with clear, independent business value first, rather than slicing the system too finely based on individual technical components or database tables.
- Errors disappear between boundaries: Utilize unified correlation IDs to trace logs, metrics, and traces across the facade, the legacy core, and the new service.
- Parallel operations never end: Assign a dedicated budget for decommissioning, data cleanup, and removing legacy infrastructure for every single migration step.
Especially during coexistence, distributed tracing is essential. OpenTelemetry describes traces as the end-to-end path of a request across multiple services. In the Rust ecosystem, Tokio’s tracing crate can emit structured events and spans and pass them to an OpenTelemetry infrastructure. This makes it instantly transparent which path processed a request, where latency is introduced, or where errors occur.
How to Measure Migration Progress
The volume of newly generated code is not a useful success metric. A Strangler migration is successful when responsibility measurably shifts away from the legacy system while operations remain completely stable. Valuable key performance indicators (KPIs) include:
- The percentage of production traffic handled by the new services.
- The number of remaining callers, background jobs, and database queries pointing to the legacy feature to be decommissioned.
- Domain-level logical discrepancies between old and new processing models.
- Error rates, P95 and P99 latency, and availability per routing path.
- Lead time for changes, testing, and deployment of the modernized domain capability.
- The financial overhead of duplicate parallel operations versus decommissioned legacy resources.
These metrics must be established before modernization begins and re-evaluated at every cutover stage. This keeps the migration roadmap strictly coupled to measurable results. A service is never rolled out simply because the schedule demands it, but because its interface contract, data migration, operational readiness, and rollback path are comprehensively validated.
When Is the Strangler Fig Pattern the Wrong Choice?
For a small, fully documented system, a complete rewrite or a direct replacement can be cheaper than supporting years of parallel operations. The pattern is also unsuitable if entry points cannot be intercepted, internal legacy calls cannot be adapted, or the legacy system must be turned off within a very short timeframe. Severe regulatory or compliance mandates may also restrict the temporary duplication of data storage.
Furthermore, your target architecture does not automatically have to consist of dozens of microservices. What matters are independent domain boundaries and clean operations. Our article How to Design Scalable Software contextualizes monoliths, modular monoliths, and microservices based on your organization’s growth stage and actual engineering needs.
Conclusion: Decommissioning Responsibility, Not Just Code
The Strangler Fig pattern transforms a high-risk system replacement into a structured sequence of verifiable architectural decisions. A robust facade controls request routing, modern domain-centric services assume logical responsibility, and a governed data transition establishes unambiguous ownership. The defining finale of every single migration step is the final decommissioning of the legacy path.
trust!NICKOL supports organizations in identifying optimal system boundaries, designing transitional architectures, and establishing a prioritized modernization roadmap. We help turn an abstract desire for modernization into an executable program with measurable outcomes and controlled risk.
FAQ: The Strangler Fig Pattern
Is the Strangler Fig pattern the same as a microservice migration?
No. The pattern describes the step-by-step replacement of a legacy system. Modernized components can be built as microservices, modules, or independent desktop/web applications. Microservices are only sensible if clear domain boundaries, scaling requirements, or independent deployments justify the additional operational overhead.
Does the source code of the legacy system need to be available?
Not necessarily for every simple, external routing scenario. A proxy can sometimes redirect specific paths without any changes to the core. However, once internal legacy calls must be disabled, data access paths modified, or legacy functions safely excised, controlled access to both the code and operations is typically required.
How do you prevent data inconsistency during migration?
By strictly designating a single System of Record for each domain capability during any given phase. Data duplicates are synchronized via explicit mechanisms and audited continuously. Idempotent processing, transaction-coupled events, and rigorous cutover criteria prevent the emergence of split-brain scenarios.
Why are Rust microservices well-suited for modernization?
Rust combines bare-metal speed with compile-time memory safety and highly predictable resource footprints. This is exceptionally valuable for high-load, security-critical, or resource-constrained services. However, Rust cannot replace a solid domain design, a sound data strategy, or mature operational practices.
When can the legacy system finally be turned off?
Only when zero production traffic, internal callers, background batch jobs, or analytical reporting processes rely on the legacy implementation. In addition, the modern data store must be thoroughly validated, operational metrics proven stable, and the agreed-upon rollback period has expired.
References and Further Reading
- Martin Fowler: Strangler Fig
- Microsoft Azure Architecture Center: Strangler Fig Pattern
- AWS Prescriptive Guidance: Strangler Fig Pattern
- AWS Prescriptive Guidance: Anti-Corruption Layer Pattern
- OpenTelemetry: Observability Primer
- Debezium Documentation: Change Data Capture Architecture
- Tokio: Getting Started with Tracing
Alexander Nickol
Software Architect & Developer
I'm passionated about programming languages and software architecture. I can offer a great versatility of skills, entrepreneurial behavior and solution-oriented thinking in areas such as Software Architecture, Software Design and Software Development in Rust and Java.
Related Articles
Migrating C++ to Rust: Incremental Modernization Without a Full Rewrite
Learn how organizations incrementally modernize legacy C++ systems with Rust to limit risks and avoid the high costs of a complete rewrite.
Performance Engineering on Solana in 2026: Optimizing Compute, Costs, and Latency
Optimize Solana compute units, priority fees, and end-to-end latency in 2026 with practical guidance for reliable, production-grade applications.
Rust for Backend Development: Pros, Cons, and Use Cases
Discover the pros, cons, frameworks, and real-world use cases of Rust backend development. Learn when Rust is the right choice for scalable, secure, and high-performance backend systems.