Branch by Abstraction and Parallel Change
What This Concept Is
Two techniques for making a large structural change while the system keeps shipping.
Parallel Change (also called expand-and-contract): a pattern for changing a widely used interface in three phases:
- Expand: add the new form alongside the old. Both work.
- Migrate: move callers from old to new, one batch at a time.
- Contract: remove the old form once no caller uses it.
Branch by Abstraction: a pattern for replacing an entire dependency (library, framework, subsystem) without a long-lived version-control branch.
- Insert an abstraction layer between client code and the current supplier.
- Migrate all clients to call through the abstraction.
- Build a new supplier behind the same abstraction.
- Switch clients (a section at a time) from old supplier to new.
- Remove the old supplier. Remove the abstraction if it no longer pays.
Both aim to keep trunk releasable. Compatibility and state evidence are still necessary at each checkpoint.
Why It Matters Here
Small-step refactoring (Clusters 1-4) assumes the change fits in a commit or two. Some changes do not: replacing an ORM, migrating a pricing engine, splitting a monolithic module. If you do those on a long-lived branch, you pay for weeks of merge conflicts, and integration and concurrent delivery become harder; other work is not inherently impossible.
Parallel Change is Change Function Declaration at scale. Branch by Abstraction introduces a supplier boundary; it is not simply a large Move Function. The discipline is the same: every commit keeps the system working.
Concrete Example
Parallel change must keep both APIs observing the same logical state. The previous sketch wrote integer-coordinate calls to an array and coordinate-object calls to a separate map. An old write followed by a new read could lose the value. Merely adding overloads does not migrate data.
// Illustrative bounded-grid adapter; Coordinate has x()/y() accessors.
public void addCell(Coordinate c, Cell value) {
addCell(c.x(), c.y(), value); // use the existing authoritative store
}
public Cell fetchCell(Coordinate c) {
return fetchCell(c.x(), c.y());
}
Keep existing bounds, empty-cell and error semantics. During migration, test old-write/new-read and new-write/old-read, overwrites and invalid coordinates. The runnable Grid lab supplies a complete sequential contract.
For supplier replacement: introduce a policy-owned boundary, adapt the old supplier, migrate callers, implement the new adapter and check its contract. Switch a bounded read-only slice first where appropriate. Stateful replacement additionally needs an explicit authority, data transfer, reconciliation and recovery design.
Common Confusion / Misconception
Forgetting the contract decision. Supporting two APIs carries documentation and maintenance costs. Assign an owner and retirement criteria during expansion. Retaining a compatibility facade can be deliberate when supported consumers still need it.
Parallel Change ≠ deprecation notice. A deprecation message without a migration timeline is an indefinite postponement. Parallel Change assumes someone actually migrates callers.
Branch by Abstraction does not mean version-control branches. The opposite: the whole point is to avoid a long-lived feature branch. The "branch" is the in-code abstraction.
How To Use It
- Inventory supported callers, persisted data and observations.
- Expand with compatible adapters and one declared state authority.
- Test mixed-version operations, error precedence and old-client behavior.
- Migrate bounded groups with metrics, an owner and a recovery decision.
- Contract only when caller inventory, compatibility policy and data evidence permit it.
Keep each checkpoint releasable; that does not mean deploy every editing step. A migration flag needs an owner and retirement criteria. A permanent compatibility facade can be intentional; do not delete it just to reach a diagram's final box.
Check Yourself
- Why is the contract phase the one most often skipped, and what is the long-term cost?
- When is Branch by Abstraction a better fit than Parallel Change, and vice versa?
- A team says "we're on a six-week refactor branch." Which of these two techniques are they not using, and why would adopting it help?
Mini Drill or Application
Pick a dependency in your system (a library, a helper module, an internal service) you suspect you might replace in the future. Sketch the Branch-by-Abstraction plan: what is the abstraction interface, which clients migrate in which order, where is the feature flag, when is the contract phase?
Engineering Extension: Exercise mixed clients
A migration test must interleave old writes and new reads on the same instance. Separate all-old and all-new tests can both pass while coexistence fails. The grid model checks overwrites and empty values through both APIs; it is an in-memory exercise, not evidence of a safe database migration.
Use the behavior contracts, labs and practice feedback to test the claim.
Read This Only If Stuck
Locate this move through the Refactoring book index. The selective reading guide gives exact consulted local locators and primary documentation. Index links are not exact chapter links.