# Strangler fig pattern: replace a legacy system without a big-bang rewrite

> The strangler fig pattern replaces a legacy system one capability at a time behind a proxy. How to pick the first slice, move the data and route traffic safely.

- URL: https://computese.com/strangler-fig-pattern/
- Author: Duong Quan Nguyen, CEO, Computese
- Published: 2026-11-04
- Updated: 2026-11-04
- Topics: Custom software

## In short
- The strangler fig pattern replaces a legacy system one capability at a time. A facade, such as a reverse proxy or API gateway, routes each request to the old code or the new, and each old part is deleted once nothing uses it.
- Choose the first slice for what it proves: one entry point, its own data, reads or replayable writes, and few hidden consumers. Cutting boundaries before the domain is clear is costly.
- Do not write to both databases from application code, because the stores can drift apart with no error. Let one system write, copy its changes with change data capture, and move each table's ownership in stages.
- Prove each step before it carries all the traffic: mirror reads to the new service, compare outputs, route a small share of customers, and keep rollback to one configuration change.
- Measure what is retired, not what is built. A slice that is migrated but not deleted leaves you running two systems, so give every toggle, sync job and adapter an owner and a removal date.

The strangler fig pattern replaces a legacy system gradually. A facade, such as a reverse proxy or an API gateway, goes in front of the old system. You rebuild one capability at a time as new code, route that capability's requests to it, then delete the old part. When nothing depends on the old system, you switch it off.

This guide covers the origin, the interception point, the first slice, the data and an NGINX routing example that we ran on NGINX 1.30.5. For the debt you are replacing, see our [technical debt](https://computese.com/technical-debt/) guide.

## Where does the strangler fig pattern come from?

During a vacation in the rain forests of Queensland in 2001, Martin Fowler saw strangler figs: vines that start in a host tree, become self-sustaining and may leave "an echo of its shape" when the host dies. He saw an analogy to how colleagues modernized legacy systems and [wrote a short post in 2004](https://martinfowler.com/bliki/OriginalStranglerFigApplication.html) proposing "to gradually create a new system around the edges of the old". In 2019 he retitled it "Strangler Fig Application", because "strangler" alone had taken on a violent connotation. His [current text](https://martinfowler.com/bliki/StranglerFigApplication.html) is dated 22 August 2024.

Fowler's case for going gradual is reduced risk. Replacements seem easy to specify, but "often it's hard to figure out the details of existing behavior", and much of that behaviour is "stuff that isn't really wanted, so building it is a waste". A strangler fig "can give value steadily", and its frequent releases "allow you to monitor its progress more carefully".

## How does the strangler fig pattern work?

There is one moving part: an interception point in front of the legacy system. The [Azure Architecture Center](https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig) calls it a facade (proxy) that routes each request either to the legacy application or to the new services. [AWS Prescriptive Guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/strangler-fig.html) starts the same way: a proxy layer that, at the start, routes all traffic to the monolith.

![A laptop and a phone send arrows toward a gate. Behind it a large grey legacy block receives most of the traffic, while one arrow curves off to a small, separate service drawn in orange.](https://computese.com/images/blog/strangler-fig-pattern/gate-splits-traffic.0102e67bed-1536.webp)

*The gate never moves; what sits behind it does.*

Each iteration after that repeats four steps:

1. Pick one capability with a clear edge, such as a URL prefix, a queue or a batch job.
2. Build it as new code, together with the data it needs.
3. Route its requests to the new code, first for a few users, then for all of them.
4. Delete the old code and the old data.

When nothing depends on the legacy system any more, the facade routes every request to the new system. Azure says you typically remove the facade then, or keep it as an adapter for legacy clients.

Routing is only part of the work. Fowler calls the code that lets old and new coexist "transitional architecture" that "will go away once the modernization is complete", and judges that the reduced risk and earlier value "outweigh its costs". By that definition the facade, any job that keeps the two data stores in step and any adapter all count, so plan their removal when you add them.

## Where do you put the interception point?

In Patterns of Legacy Displacement, published on Fowler's site, Ian Cartwright, Rob Horn and James Lewis call the technique [event interception](https://martinfowler.com/articles/patterns-legacy-displacement/event-interception.html): intercept events passing between components and route some of them to new ones. They add that if you use the strangler fig pattern, "you will also be using some form of Event Interception". Use the interception point your system already has. Our [system integration](https://computese.com/system-integration/) guide compares the wider approaches.

| Interception point                                        | Routes by                                     | Fits when                                       | Watch for                                                            |
| --------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------- |
| [Reverse proxy](https://computese.com/reverse-proxy/)                          | Host, port, path, query parameters, headers   | A web app or HTTP API reachable by URL          | Routing on form data may need custom code                            |
| [API gateway](https://computese.com/the-future-of-enterprise-api-development/) | The published endpoint, sometimes the payload | Clients already call a published API            | A single point of failure or a bottleneck                            |
| [Message router](https://computese.com/event-driven-architecture/)             | Message headers or content                    | Systems already talk through queues or topics   | A legacy consumer may need reconfiguring to read the new destination |
| Abstraction in the code                                   | Which implementation a caller uses            | Calls inside the monolith, with no network edge | You must be able to change the source code                           |

The routing layer can [become a single point of failure or a performance bottleneck](https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig), so run more than one instance and watch its latency.

## Which capability should you move first?

Look for seams: places where the system can be split. Fowler says a well-designed system would already have them, but "such systems are unicorns", so finding them is real work. AWS warns that [premature decomposition](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/strangler-fig.html) "can be costly, especially when the domain isn't clear", and suggests domain-driven design and event storming to find the boundaries. In a [2019 study](https://arxiv.org/abs/1906.04702) of 14 systems, based on 16 interviews with German-based software professionals, finding the right service cut "was a major technical challenge". One participant said, "Our service cut was not good, we sometimes needed to touch two or three services for a change."

The first slice should prove the route (routing, deployment, logging, data sync and rollback), so choose it for low risk. Later slices can deliver what the business is waiting for, and Azure notes the approach lets you do high-ROI replacements before low-ROI ones. Score candidates against this table:

| Criterion       | A good first slice                            | A poor first slice                                       |
| --------------- | --------------------------------------------- | -------------------------------------------------------- |
| Entry point     | One URL prefix, queue or API operation        | Logic reached from dozens of screens                     |
| Data            | Owns its tables, few joins to the rest        | Tables that every module joins to                        |
| Safe to compare | Read-only, or writes you can replay in a copy | Payments, emails and other effects that cannot run twice |
| Consumers       | Known callers                                 | Reports, exports and batch jobs that read its tables     |
| Value           | Changes often, or costs a lot to run          | Stable code that nobody touches                          |

In an order system, a read-only order status page scores well. Pricing and tax rules, which every order path calls, wait until the domain is clearer.

## How do you move the data?

The data is what makes the pattern hard, because old and new code must agree on it while both run.

### Share the database first

In Azure's database example, the new service first reads and writes the monolithic database for its domain tables, while the legacy system serves everything else. Azure's guidance is to make sure that "both systems can access these resources at the same time". That needs no sync, but it couples the new code to the legacy schema, so keep the access in one data layer. AWS lists it as a best practice that "the microservice should own its data."

### Why not write to both databases?

The tempting shortcut is a dual write: the application updates the old store and the new one in the same code path. Martin Kleppmann describes [two ways it fails](https://martin.kleppmann.com/2015/05/27/logs-for-data-infrastructure.html). Two clients writing the same key can reach the two stores in different orders, so the stores "permanently remain inconsistent" until something overwrites the key, and no error is raised. And a process that fails between the two writes leaves one store with the change and the other without it.

![An application box writes to two databases. The upper write arrives and its database holds three documents; the lower arrow breaks short of its database, which is orange and holds only two.](https://computese.com/images/blog/strangler-fig-pattern/two-writes-diverge.881a28bc43-1536.webp)

*Two writes are not one transaction, so one store can end up a record behind the other.*

### Copy changes with change data capture

Instead, let one system write and copy its changes to the other. Azure's example loads the domain tables' history with an extract, transform and load (ETL) process, runs change data capture (CDC) to sync changes from the monolithic database, and validates consistency before cutover. The [PostgreSQL connector](https://debezium.io/documentation/reference/stable/connectors/postgresql.html) in Debezium 3.7 takes a consistent snapshot the first time it connects, then continuously captures row-level changes that were committed.

![A legacy database pushes a strip of log tiles out of its side. An orange reader clipped over the strip sends envelopes along one arrow to the separate database of a new service.](https://computese.com/images/blog/strangler-fig-pattern/log-tailing-sync.53aed7747b-1536.webp)

*The new database is fed from the log, so the legacy application never has to know it exists.*

Two caveats matter. PostgreSQL logical decoding "does not support DDL changes", so the connector cannot report schema changes to consumers, and you must plan how a change to a synced table reaches the new database. And after a failure Debezium provides at-least-once delivery, so consumers "should always anticipate some duplicate events": write idempotently, for example as an upsert by primary key. Our [change data capture](https://computese.com/change-data-capture/) guide covers the mechanics and the outbox pattern, and our [dead letter queue](https://computese.com/dead-letter-queue/) guide covers idempotency keys.

### Hand over each table in stages

Cartwright, Horn and Lewis describe a team that extracted one domain in [four stages](https://martinfowler.com/articles/patterns-legacy-displacement/event-interception.html):

1. **Dark launch.** The new services keep their data in sync with the legacy system but have no consumers, so the team can validate 100% parity with the legacy data store.
2. **Intercept reads.** All reads for the domain go to the new services. Writes still go to the legacy store.
3. **Intercept writes.** Writes move too. The new service becomes the system of record, and downstream users such as reports must move to it.
4. **Move the business rules** into the new services.

At each stage only one system takes the writes for that data, which is the rule to copy.

> [!WARNING]
> Do not drop the legacy tables when traffic switches. Azure says you can roll back to the monolithic database while the domain tables and sync still exist. After you remove them, you must restore those objects and replay data changes, which "significantly increases effort and risk". Remove legacy objects as a deliberate final step for each domain, after the new system is validated.

## How do you route one path to the new service?

This NGINX example keeps everything on the legacy system except `/invoices`, which it sends to a new billing service for 10% of customers. We ran this exact file on NGINX 1.30.5, with two throw-away servers standing in for the back ends. The hostnames, ports and the customer cookie are examples.

```nginx
log_format strangler '$time_iso8601 $request_method $uri $status '
                     'upstream=$upstream_addr upstream_status=$upstream_status';

upstream legacy  { server legacy.internal:8080; }
upstream billing { server billing.internal:3000; }

# Hash a stable customer ID, so each customer stays on one side.
split_clients "${cookie_customer_id}" $invoices_to {
    10%  billing;
    *    legacy;
}

server {
    listen 80;
    server_name app.example.com;
    access_log /var/log/nginx/strangler.log strangler;
    proxy_set_header Host $host;

    location / {
        proxy_pass http://legacy;
    }

    # Slice 1: invoices.
    location = /invoices {
        proxy_pass http://$invoices_to;
    }
    location ^~ /invoices/ {
        proxy_pass http://$invoices_to;
    }
}
```

Because the `proxy_pass` target is a variable holding an upstream group's name, NGINX [looks the name up among the defined groups](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) and needs no resolver. Four points come from NGINX's documentation, and our run reproduced each:

- **Exact match for the bare path.** A prefix location that ends in a slash and uses `proxy_pass` makes [NGINX answer `/invoices` with a 301](https://nginx.org/en/docs/http/ngx_http_core_module.html) to `/invoices/`. Without the `location = /invoices` block, our run returned `301 Moved Permanently`.
- **The Host header.** By default NGINX sends `Host: $proxy_host`, the name in `proxy_pass`, not the client's. Without `proxy_set_header Host $host;`, our legacy stand-in saw `host=legacy`.
- **Regular expressions beat plain prefixes.** NGINX remembers the longest matching prefix, then tries regular expressions in order and uses the first match, unless the prefix has `^~`. Without it, a legacy rule such as `location ~ \.json$` took `/invoices/1.json` back in our run.
- **Sticky buckets.** [`split_clients`](https://nginx.org/en/docs/http/ngx_http_split_clients_module.html) hashes the string with MurmurHash2, so a customer ID always lands on the same side. In our run, 10% sent 200 of 2,000 made-up customer IDs to billing, and 25% kept those 200 and added 286. Requests without the cookie all land on one side, which was billing in our run.

To roll back, delete the `10% billing;` line, because NGINX rejects `0%` as an invalid percent value, then run `nginx -t` and `nginx -s reload`. To measure the share moved, count the `upstream=` column that the log format writes:

```bash
awk '{print $5}' /var/log/nginx/strangler.log | sort | uniq -c
```

## Which patterns make each step safer?

Four patterns recur around the routing layer. Treat each as transitional, with a retirement trigger written down when you add it.

[Branch by abstraction](https://martinfowler.com/bliki/BranchByAbstraction.html) works from inside the code. You create an abstraction layer between one section of client code and the current supplier, call the supplier only through it, build a new supplier behind the same layer, switch over, and delete the old supplier.

An [anti-corruption layer](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) (ACL) is an adapter that translates requests between systems that do not share the same semantics, so the legacy model does not leak into the new design. AWS suggests building it inside the monolith as a class specific to the migrated service, such as `UserServiceFacade`, and decommissioning it once all dependent services have moved. Azure notes it adds latency and an extra service to maintain.

![A large grey legacy block with a square plug and a small new service with a round plug are joined by an orange adapter that has a square socket on one end and a round socket on the other.](https://computese.com/images/blog/strangler-fig-pattern/adapter-between-plugs.e71dca6c44-1536.webp)

*The adapter takes the shape difference, so neither side has to change its plug.*

A feature toggle is a switch that decides who gets the new code, and the percentage in the NGINX example works as one. Pete Hodgson writes that [release toggles](https://martinfowler.com/articles/feature-toggles.html) "are transitionary by nature" and "should generally not stick around much longer than a week or two", although product-centric toggles may need to remain longer. A migration switch can outlast that, so treat toggles as inventory with a carrying cost: add a removal task when you add the toggle, or give it an expiration date.

A parallel run executes old and new on the same input and compares the results while users keep receiving the old answer. GitHub's [Scientist](https://github.blog/developer-skills/application-development/scientist/) does it inside the code: it runs the original code (the control) and the rewrite (the candidate), returns the control's result, and records differences and timings. GitHub only uses it on read operations, because a candidate that writes to the same database is "dangerous and incorrect". At the proxy, NGINX's [`mirror` directive](https://nginx.org/en/docs/http/ngx_http_mirror_module.html) sends a copy of each request to a second location and ignores the response. Add this inside the server block above:

```nginx
# Dark launch: legacy answers; billing gets a copy and its answer is dropped.
location /reports/ {
    mirror /mirror-reports;
    proxy_pass http://legacy;
}
location = /mirror-reports {
    internal;
    proxy_pass http://billing$request_uri;
}
```

In our run, legacy answered the customer and the billing stand-in received a copy of both a GET and a POST. A mirrored write runs on the new service too, so mirror reads, or give the new service a copy of the data with its side effects switched off.

![A laptop's request is copied by a splitter. The legacy block answers the laptop, while the new service's copy of the answer goes only to an orange balance scale that weighs it against the legacy answer.](https://computese.com/images/blog/strangler-fig-pattern/mirror-and-compare.7852a6b23b-1536.webp)

*The customer only ever sees the old answer; the new one is weighed in private.*

We used the same comparison in a [legacy Java modernization](https://computese.com/work/legacy-java-modernization/) assessment and proof of concept: a slice moved from Struts to Spring Boot was checked against a golden baseline of the old system's screens, database writes and report files.

## How do you measure progress without fooling yourself?

Progress should look like the picture below: the legacy block shrinks slice by slice while the facade stays until the last one goes.

![Three panels show the same gate in front of a legacy block. Across the panels the block shrinks from six parts to a dashed outline while new service boxes multiply, above one orange arrow for time.](https://computese.com/images/blog/strangler-fig-pattern/legacy-shrinks.1e1c3d927a-1536.webp)

*Progress is the old block getting smaller, not the new boxes getting more numerous.*

A slice that is "migrated" but not deleted still costs you two systems to run, patch and keep in sync, and the risk in this pattern is a facade and two systems that live on for years. In the 2019 study, just three of the 14 systems had finished their migration activities, and the time frames ranged from 1.5 to over 3 years, "based on the already finished migrations or expected dates". Measure what is retired, not what is built:

| Measure                          | How to get it                                                                        | Watch for                                              |
| -------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| Share of requests on new code    | Count the upstream column in the proxy log                                           | At 100% a slice is not done until its old code is gone |
| Capabilities retired             | Legacy endpoints, jobs and tables deleted, from a list kept in the repository        | "Migrated" and "retired" are different states          |
| Transitional parts still running | Sync jobs, adapters, mirror rules and toggles, each with an owner and a removal date | The count rises with a slice and must fall after it    |

## Strangler fig, rewrite, refactor or replace: which fits?

| Approach               | What you do                                              | Fits when                                            | Main risk                                                        |
| ---------------------- | -------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------- |
| Big-bang rewrite       | Build the whole replacement, then cut over once          | The system is small and its behaviour is fully known | Hidden behaviour surfaces at the cut-over, and new features wait |
| Refactor in place      | Restructure the internals, same behaviour, same platform | The design is sound and tests exist or can be added  | It does not change the platform you are stuck on                 |
| Replace with a product | Buy or subscribe, migrate the data, retire the module    | The capability is not what sets your business apart  | Process change and data migration become the work                |
| Strangler fig          | Route by capability, move each slice, delete the old     | The system works, cannot stop, and has seams         | Running two systems, and a migration that drags on               |

AWS notes that while an application is being refactored, adding features is "extremely hard or even impossible". In the 2019 study, most companies preferred a rewrite over splitting up existing code bases, often because they had no suitable decomposition approach, and the Strangler pattern was used in seven cases. Our [cloud migration strategy](https://computese.com/cloud-migration-strategy/) guide places refactoring among its seven migration treatments.

The pattern is the wrong tool in some situations. Azure's page, last updated 2 June 2026, lists four: requests to the back end cannot be intercepted, you cannot access the legacy source code, you are migrating a small system and replacing it is simple, or you need to fully decommission the original solution quickly. AWS adds that for small applications, rewriting may be more efficient than migrating. Two more situations make it hard rather than impossible. Azure notes that a centralized database serving many domains becomes difficult to manage and improve because of its cross-domain dependencies, so untangle ownership of the data first. And Fowler warns that without a change in organizational culture and leadership, the new systems "will end up in a similar mess".

## What goes wrong, and what stops it?

| Symptom                                  | Likely cause                                            | Fix                                                                                              |
| ---------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Everything is down when the proxy is     | The facade is a single point of failure or a bottleneck | Run several instances, load test the routing layer and keep its configuration in version control |
| Old and new disagree about a customer    | Dual writes, or two systems writing the same table      | One writer per table, CDC for the copy and a scheduled comparison query                          |
| A report breaks after a slice moves      | A hidden consumer read the legacy table or file         | List every consumer before the move; in the staged example, downstream reports had to move       |
| One change touches two or three services | The slice boundary is wrong                             | Cut by business capability, and merge slices that always change together                         |
| Legacy keeps running past its end date   | Slices were migrated but never deleted                  | Make deletion part of done, track retired capabilities and put a date on the facade              |

## A migration plan you can start this month

1. **Write the finish line.** Define what "legacy switched off" means, who owns it, and what the business gets from each slice.
2. **Map the seams and the consumers.** List every entry point (URLs, queues, batch files, tables, reports) and who calls or reads each.
3. **Put the facade in with no behaviour change.** Route 100% to legacy, deploy the proxy through your CI/CD pipeline, and measure latency and errors through it. Decide how a signed-in user's session reaches the new service.
4. **Choose the first slice and build it with its data.** Share the database first, and add CDC when the slice needs its own.
5. **Dark launch.** Mirror or compare read traffic, fix every difference, and move on only when the differences stay at zero.
6. **Switch a small share**, raise it in steps, and keep rollback to one configuration change.
7. **Retire.** Delete the old code, tables, jobs, toggles and access, then take the next slice. Each slice goes through the same [software development life cycle](https://computese.com/software-and-development/) as any other change.
8. **Remove the facade** after the last slice, or keep it as an adapter if legacy clients still need it.

Our [cloud transformation](https://computese.com/services/cloud-transformation/) work moves workloads in rehearsed waves, each with entry criteria, a cutover plan and a tested rollback, and our [custom software development](https://computese.com/services/custom-software-development/) work starts with one deployable application and splits out services only where load or teams need them.

## Key terms
- **Strangler fig pattern**: A way to replace a legacy system gradually. New code grows around the old system, requests move to it one capability at a time, and the old system is switched off when nothing depends on it.
- **Interception point (facade)**: The proxy, gateway or routing layer in front of the legacy system that sends each request to the old code or to its replacement. Azure calls it a facade (proxy).
- **Seam**: A place where a legacy system can be split so a piece can be extracted and replaced. Message consumers, HTTP APIs, SQL connections and batch jobs are technical seams that event interception uses.
- **Anti-corruption layer (ACL)**: An adapter between systems that do not share the same semantics. It translates requests so the legacy model does not leak into the new design. Eric Evans first described it.
- **Branch by abstraction**: A technique for replacing a part from inside the code: put an abstraction layer in front of the old implementation, build the new one behind the same layer, switch callers over, then delete the old one.
- **Dark launch**: Running a new service that has no consumers yet, so its data can be checked against the legacy system before anyone depends on it.
- **Change data capture (CDC)**: Creating an event stream from the entries a database appends to its transaction log, so another system can stay in step with committed changes without the application writing twice.
- **Dual write**: An application updating two systems, such as the old and new database, in one code path with no shared transaction. A failure or a race between the two writes can leave them different.
- **Transitional architecture**: Code and infrastructure that exist only so old and new systems can coexist, such as the facade, sync jobs and adapters. It goes away when the modernization is complete.

## Common questions

### What is the strangler fig pattern?

It is a way to replace a legacy system gradually. A facade, such as a reverse proxy or API gateway, sits in front of the old system and routes each request to the old code or to its replacement. You move one capability at a time, delete each old part once it is unused, and decommission the legacy system when nothing depends on it.

### Where does the name strangler fig come from?

Martin Fowler named it after strangler figs he saw in the rain forests of Queensland in 2001. The vines grow down a host tree, and the host may die, leaving the fig as an echo of its shape, which struck him as an analogy for replacing legacy software gradually. His 2004 post was titled 'Strangler Application'; in 2019 he renamed it 'Strangler Fig Application' to reinforce the botanical metaphor and reduce a violent connotation.

### How is the strangler fig pattern different from a big bang rewrite?

A big bang rewrite builds the whole replacement and cuts over once. The strangler fig pattern moves one capability at a time behind a routing layer, so each step meets real traffic before the next begins. Fowler's 2004 post calls reduced risk the most important reason to consider it over a cut-over rewrite.

### Is the strangler fig pattern only for microservices?

No. Fowler describes a gradual approach to legacy modernization in general, and Azure describes replacing pieces of functionality with new applications and services. AWS's page targets moving a monolith to microservices. The new side can be one well-structured application or several services.

### When should you not use the strangler fig pattern?

Azure lists four cases: requests to the back end cannot be intercepted, you cannot access the legacy source code, the system is small and replacing it is simple, or you must fully decommission it quickly. AWS adds that for small applications, rewriting may be more efficient than migrating. Azure also notes that a database serving many domains becomes difficult to manage and improve because of its cross-domain dependencies, so untangle ownership of the data first.

### How do you handle the database in a strangler fig migration?

Start with the new service reading and writing the legacy tables, then give it its own database kept in sync with change data capture, with one system writing at a time. Avoid dual writes from application code, because they can leave the two stores different with no error. Remove the legacy tables as a separate, deliberate step after the new system is validated.

### How long does a strangler fig migration take?

It varies a lot. Fowler's 2004 post pictured the new system growing slowly over several years. In a 2019 study of 14 systems, time frames ran from 1.5 to over 3 years, based on finished migrations or expected dates, and only three of the systems had finished. Set a decommission date for each slice so the total does not drift.

## Sources
1. [Strangler Fig](https://martinfowler.com/bliki/StranglerFigApplication.html), Martin Fowler
2. [Original Strangler Fig Application](https://martinfowler.com/bliki/OriginalStranglerFigApplication.html), Martin Fowler
3. [Strangler Fig pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig), Azure Architecture Center, Microsoft Learn
4. [Strangler fig pattern](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/strangler-fig.html), AWS Prescriptive Guidance
5. [Event Interception (Patterns of Legacy Displacement)](https://martinfowler.com/articles/patterns-legacy-displacement/event-interception.html), Martin Fowler
6. [Branch By Abstraction](https://martinfowler.com/bliki/BranchByAbstraction.html), Martin Fowler
7. [Anti-Corruption Layer pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer), Azure Architecture Center, Microsoft Learn
8. [Microservices Migration in Industry: Intentions, Strategies, and Challenges](https://arxiv.org/abs/1906.04702), arXiv (ICSME 2019)
9. [Using logs to build a solid data infrastructure (or: why dual writes are a bad idea)](https://martin.kleppmann.com/2015/05/27/logs-for-data-infrastructure.html), Martin Kleppmann
10. [Debezium connector for PostgreSQL](https://debezium.io/documentation/reference/stable/connectors/postgresql.html), Debezium Documentation
11. [Feature Toggles (aka Feature Flags)](https://martinfowler.com/articles/feature-toggles.html), Martin Fowler
12. [Scientist: Measure Twice, Cut Once](https://github.blog/developer-skills/application-development/scientist/), The GitHub Blog
13. [Module ngx_http_core_module (location)](https://nginx.org/en/docs/http/ngx_http_core_module.html), NGINX
14. [Module ngx_http_proxy_module (proxy_pass, proxy_set_header)](https://nginx.org/en/docs/http/ngx_http_proxy_module.html), NGINX
15. [Module ngx_http_split_clients_module](https://nginx.org/en/docs/http/ngx_http_split_clients_module.html), NGINX
16. [Module ngx_http_mirror_module](https://nginx.org/en/docs/http/ngx_http_mirror_module.html), NGINX
