Big-bang rewrites are risky: months without value, then one scary switch. Two techniques make large changes incremental.
flowchart LR
subgraph BBA["Branch by abstraction (inside the code)"]
C["Callers"] --> I["Interface: PaymentGateway"]
I --> OLD["LegacyPayments"]
I -.->|"flag"| NEW["StripePayments"]
end
subgraph SF["Strangler fig (around the system)"]
U["Users"] --> PX["Proxy / gateway"]
PX -->|"/cart"| NS["New cart service"]
PX -->|"everything else"| MONO["Legacy monolith"]
end
%% step C: All callers are changed to use an abstraction instead of the old component directly.
%% step I: The interface hides which implementation runs.
%% step NEW: The new implementation is built behind the interface and switched on with a flag, gradually.
%% step PX: For whole systems, a proxy sits in front of the legacy system.
%% step NS: Routes move one by one to new services until the legacy system has nothing left.
Branch by abstraction (steps)
- Create an abstraction (interface) over the component you want to replace.
- Change all callers to use the abstraction — still backed by the old implementation.
- Build the new implementation behind the same interface, committing to main (no long-lived branch).
- Switch callers gradually with a feature flag; compare results if you can.
- Remove the old implementation and, optionally, the abstraction.
It is "branching" in the code instead of in Git — trunk-based development stays possible during a big change.
Strangler fig (steps)
- Put a proxy or API gateway in front of the legacy system (no behaviour change).
- Pick one route or capability with clear boundaries (e.g.
/cart). - Build it as a new service; route that path to it — maybe 5% first.
- Repeat route by route; the legacy system shrinks.
- Switch off the legacy system when no traffic reaches it.
Named after a fig that grows around a host tree until it replaces it.
Data during a strangler migration
The hardest part. Options: the new service owns new data and calls the monolith for the rest; sync with change data capture; or an anti-corruption layer that translates between the old model and the new one.
When to use which
| Situation | Technique |
|---|---|
| Replace a library, ORM, payment provider inside one codebase | Branch by abstraction |
| Break a monolith into services, move to a new platform | Strangler fig |
| Both at once | Common: strangle the system, abstract inside each piece |
Traps: a strangler proxy that becomes a permanent second system; extracting the most tangled module first; long-lived feature branches instead of abstraction; never deleting the old path.