Branch by Abstraction: Swapping a Subsystem In Place
Replace a subsystem inside one codebase while trunk stays green.
Branch by Abstraction swaps a subsystem inside the same codebase. You introduce an abstraction over the current implementation, build the replacement behind that abstraction, switch via a feature flag, and remove the old implementation. The trunk stays green the entire time, which is what makes this safe to do continuously rather than on a long-lived branch.
When to use this play#
Use Branch by Abstraction when you are replacing a subsystem and two conditions hold: the seam is in code rather than at a network boundary, and trunk-based development is the norm.
Typical subsystems:
- A database engine.
- A payment processor.
- A search backend.
- An auth library.
If the seam lives at the network or HTTP layer instead, Strangler Fig is the better fit.
How to run it#
1. Identify the subsystem and its callers. Be precise about where the subsystem starts and ends and who depends on it.
2. Introduce an interface or adapter over the current implementation. Wrap what exists today behind a thin abstraction that captures how callers actually use it. Trunk stays green; nothing has changed behaviorally yet.
3. Route all callers through the abstraction. Funnel every dependency through the new interface so there is a single switching point. This step alone is valuable even before the replacement exists.
4. Build the new implementation behind the abstraction. Develop the replacement against the same interface, in the same codebase, without disturbing callers.
5. Switch by feature flag, with rollback. Flip from old to new behind a flag so you can flip back instantly if something is wrong. Switch in production deliberately, not all at once if you can avoid it.
6. Remove the old implementation. Once the new one is proven, delete the old implementation, and remove the abstraction too when it no longer earns its keep.
How it differs from Strangler Fig#
Both plays replace something incrementally, and they are easy to confuse, so choose by where the seam lives. Strangler Fig works at the network or HTTP boundary, routing requests between an old service and a new one through a proxy. Branch by Abstraction works inside a single codebase, routing function calls through an interface. If you are replacing a whole service that already has a clean network edge, reach for Strangler Fig. If you are replacing a library, an engine, or a module that callers reach through ordinary in-process calls, this is the play.
The shared discipline is the same: introduce a single switching point, build the replacement behind it, switch with a way to switch back, and then delete what you replaced. The difference is only whether that switching point is a router or an interface.
Common traps#
- Designing the "perfect" abstraction before swapping anything. Analysis paralysis here is the classic failure. Build an interface good enough for the swap and refine it as you learn; do not let it become the project.
- Never finishing. If both implementations live behind the abstraction forever, you have added complexity and replaced nothing.
- Long-lived feature flags. A switching flag that is never cleaned up becomes permanent dead weight and a source of confusion. Remove it once the swap is complete.
Signals it's working#
- Trunk stays green through every step.
- All callers go through a single abstraction, giving you one switching point.
- The flag flips cleanly between old and new in production, and flips back just as cleanly.
How it ends#
It ends when the new implementation is live, the old one is deleted, and the temporary feature flag is removed. Keep the abstraction only if it earns its place as a genuine seam; otherwise remove it too, so the codebase ends up simpler than the day you started, not more layered.