Branching by abstraction is a technique used for making large-scale changes gradually, while simultaneously continuing to release the system. This is an important part of trunk-based development, and is critical for creating a continuous integration and continuous delivery (CI/CD) pipeline.
In a non-CI/CD situation, the standard way to make these types of changes is to use feature branches in version control, but this isn’t compatible with continuous integration (because code on a branch isn’t integrated). But how is it possible to keep integrating code while a large new feature is being built, or a significant amount of code is being refactored? How can we satisfy the necessary constraints of a) ensuring that other members of the development team can continue to work on other code which is dependent on the code being changed, and b) that the whole codebase is releasable at any time?
The Branch by Abstraction Process
For the sake of example, let’s say that we’re refactoring a significant portion of code in the back-end of an application. There is a decent amount of client-facing code that is dependent on the existing, old implementation, and we are trying to redesign it.
In order to make this work in a CI environment, here is the branch by abstraction process involved:
- Add an abstraction layer around all components using the old implementation. Change the references so that the components are referencing the abstraction instead of the old implementation’s code directly.
- Repeat step 1 for all client references, so that now all references to the old implementation go through the abstraction layer.
- When building the new implementation, first implement the features that will be needed by one piece of client code, then switch that piece of code alone to reference the new implementation (still behind an abstraction layer). Before a given part of the new implementation is completed, hide it behind a feature flag (aka feature toggle), turned off. When each part is finished, turn the flag on.
- Repeat step 3 until all client code uses the new implementation.
- Delete the old implementation, the feature flag(s), and possibly also the abstraction layer.
There are many variations on this process: for example, it may not be possible to swap out each part of the new implementation individually, and it might need to all be swapped over all at once. In any case, the abstraction layer allows an easy transition between one implementation and another by allowing both to coexist in the same system.
Benefits and Drawbacks of Branching by Abstraction
In addition to the central benefit of being able to migrate large features easily in continuous integration, there are a few side benefits of branching by abstraction. For one, pausing and resuming migrations is simple and easy, because the new implementation is guarded by the system. On a standard feature branch, it’s possible to pause a migration, but it’s more difficult to resume. For another, canceling an existing project, while not quite as cheap as it is for a long-running feature branch, is only slightly more expensive.
Branching by abstraction is not always the best option, though. For example, in situations where the customer can choose for themselves when they upgrade their version of your software, it won’t work because the entire system must be upgraded at once.