Branch by abstraction enables continuous deployment by isolating large code changes through an abstraction layer. Combined with feature flags, it allows developers to test updates safely, minimizing disruptions and easing the transition to new implementations.
Branching by abstraction is a pattern used for making large-scale changes gradually, while simultaneously continuing to release your application. 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 large-scale 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). So 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 you satisfy the necessary constraints of a) ensuring that other members of your 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? And how can you ever be sure that you’re releasing safely?
For the sake of example, let’s say that you’re refactoring a significant portion of the code in the backend of an application. There is a decent amount of client-facing code that is dependent on the existing, old implementation, and you cannot risk breakage, nor can you release the entire set of changes at once.
In order to manage your updates in a continuous integration environment, you’d traditionally want to implement branching by abstraction via the following process:
There are many variations on this process: for example, it may not be possible to swap out each part of the new implementation individually, it might need to all be swapped over all at once. Martin Fowler points out that, “in the simplest case you build the entire abstraction layer, refactor everything to use it, build the new implementation, and then flick the switch. But there’s various ways to break it up. You may not build the whole abstraction layer, just a subset of functionality, migrate that and then do another hunk of functionality (providing new and old can co-exist.) Otherwise, you may shift some calling code onto the abstraction and have that implemented both ways before you move the rest.”
In any case, the abstraction layer allows an easy transition between one implementation and another by allowing both to coexist in the same system.
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. Because your release schedule is completely decoupled from your architectural changes, pausing and resuming migration is easy and cheap, because the new implementation is guarded by the system. Aka, when your leadership team comes up with a high priority feature, or CS needs a bug fix to ship ASAP you can stop and resume with relative ease. On a standard feature branch, it’s possible to pause a migration, but it’s more difficult to resume.
Looking for additional benefits? How about this… Because you’re only interfacing with the abstraction layer, the breadth of merge conflict you could run into is restricted to that abstraction layer. Without the abstraction layer, your entire codebase refactor would be sitting in a feature branch for potentially quite some time, earning itself a variety of merge conflicts that you may never be able to fully resolve.
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.
So, you need the utility of branching by abstraction in your modern CI/CD release pipeline? Enter feature flags. Feature flags (sometimes referred to as feature toggles) can enhance your abstraction layer. Your development teams can rebuild legacy features behind flags, and when you’re ready you can safely flip your entire application over to use the new code, deleting your feature flag and deprecating the legacy code you replaced simultaneously.
How would this work in practice?
One clear benefit of deploying feature flags as you migrate from legacy systems is the ability to test in production and thus release safely. When adding the complexity of an abstraction layer to your legacy codebase, you can never be sure your staging environment is a close enough match to catch any breaking issues. Feature flags allow you to safely test in production and know that when you release you won’t need to immediately rollback.
Feature Management and Experimentation by Harness gives you the confidence to move fast without breaking things. Set up feature flags and safely deploy to production, controlling who sees which features and when. Connect every flag to contextual data, so you can know if your features are making things better or worse and act without hesitation. Effortlessly conduct feature experiments like A/B tests without slowing down.