When we add cool new features to our Yoast plugins, we use Git workflows to ensure that development can proceed smoothly. In this article, I'll talk about two workflows we use when building bigger features: using feature branches and feature flags. Each workflow has its advantages and disadvantages. After reading this article, you'll know more about which workflow is suitable for which situation, and what to be careful about when using these workflows.
Our basic workflow
The Yoast SEO plugins are our main products. The code for the plugins is hosted in Git repositories. This codebase is always in flux. We have multiple teams of developers working on the plugins at the same time. We also want to ship new features and bug fixes as quickly as possible. That's why we follow a cycle of bi-weekly releases.
It's important to keep order in such a fast-changing environment. For this, we follow a Gitflow workflow. This means that all of our repositories for plugin development have a main development branch, on which we introduce changes such as bug fixes or new features. These changes are implemented via story branches branched off from the main development branch. Next to the main development branch, there’s also a master branch from which releases are deployed every two weeks.
In many cases, we work on small, self-contained enhancements or fixes for our products. Small changes like this get reviewed and tested before we merge them to the main development branch. One or two developers will work on one small change and create a pull request to merge it to the main development branch. Once the change has been merged, these enhancements or fixes are ready to be included in the next release.
Sometimes this basic workflow isn’t sufficient, though. We also build cool, big features that are more complex in their development. Instead of just one or two people, a whole team will work on such a feature. The feature's implementation will not be covered in a single PR, but in many PRs that get merged into the codebase over a longer period of time. Each of these PRs will implement part of the feature, but not every PR can necessarily stand on its own. It's not possible to ship just part of the feature. Sometimes the feature is only stable enough to be shipped at the very end of development.
Remember, however, that the main development branch should remain stable, because it will be the base for a new release every two weeks. This means that we need to make use of some more advanced workflows on top of the basic Gitflow. Below, I’ll outline two such workflows for the implementation of bigger features: feature flags and feature branches.
Using a feature flag
When using a feature flag, you will still use the main development branch as your base branch. To keep your changes separated, you’ll implement them behind a feature flag. That means that you can run the code in two versions. When the feature flag is switched off, everything will work as usual. Only when it’s switched on, your code will be executed.
Advantages of feature flags
One advantage is that you can keep development based on the main development branch. That means you don’t have to keep a separate branch up to date. Consequently, your development base will not diverge as much from the real main code base. However, keep in mind that you still need to make sure that the code you cordon off behind the feature branch stays compatible with the rest of the code base. But in general, you can be sure that the code of your new feature lives in the most up-to-date environment.
Another advantage is that a feature flag makes it easy to run a public beta. That's because both the code for the new and the old functionality live side-by-side in your codebase. By adding some graphic user interface that allows a user to switch the feature on and off, you can get feedback from users about your new functionality before you make it the new default.
Disadvantages of feature flags
When using a feature flag, you need to keep old functionality intact that might have become obsolete with your changes. That’s because you need to keep a working version of the old functionality running on your main development branch alongside your changes.
One disadvantage here is that this process can become messy. You need to keep an overview of all functionality that falls outside your feature flag, but which will be made obsolete by your changes. If you have a lot of changes, that can be a daunting task!
Another disadvantage that comes with this approach is that you need to have a big clean up at the end. Once your feature is ready to go into production, you'll have to remove all the old functionality. You also need to make sure that you don't break anything in the process!
Using a feature branch
An alternative to using a feature flag is a feature branch. Using a feature branch means that in the first instance, you make a copy of your main development branch. Consequently, you can branch off that feature branch for all smaller parts of your big feature. PRs then get merged back into the feature branch. That way, a feature slowly is built up on the feature branch, and will only get merged back to the main development branch once it's finished.
Advantages of feature branches
A feature branch gives you a clean working copy of your codebase. You don't need to keep all of the old code that your new feature will replace, but you can refactor it as you go along. Your feature branch will be a clear representation of what your codebase will look like after you’ve implemented your feature, including not only all additions, but also all structural changes and all deletions.
Working on a feature branch gives you a bit more freedom to introduce some temporary instabilities. Ideally, every PR you merge should leave the branch into which you merge it in a stable state. But sometimes it’s easier to split up work into multiple smaller subtasks, where the first of the subtasks might introduce some temporary instability (e.g., making some changes to your database structure) and your later subtasks make it all work again (e.g., populating and accessing the new database structure). Introducing such temporary instabilities on your main development branch could get in the way for colleagues, who might be working on something completely unrelated and expect the main development branch to be stable for that purpose. Having your own feature branch avoids this issue.
Disadvantage of feature branches
The one big disadvantage of using a feature branch is that you always need to keep your feature branch up-to-date with your main development branch. Otherwise, your feature branch will become outdated.
If you don't keep your feature branch updated, it will diverge more and more from your main development branch as time goes by. This might result in complex and hard to track merge conflicts once you merge your feature branch back into the main development branch. Also, it would mean that you can’t be sure whether your changes are still compatible with everything that’s happened on the main development branch in the meantime. Not only does that mean that you'd have a bunch of unexpected bugs to fix at the end of your project, but you might also end up with an unstable version of your main development branch.
That’s a situation you’d usually want to avoid. So, when you use a feature branch, be aware that it costs time to regularly merge the main development branch back into the feature branch, including the resolution of potential merge conflicts.
Conclusion: pick the workflow that suits your needs
I have outlined two Git workflows that you can use to manage the implementation of big new features: feature flags and feature branches. None of these two is inherently better or worse. Which one you choose will depend on multiple factors, such as the size of your project, and whether you only want to add new functionality, or also refactor a lot of old code.
If you want to run a public beta test of a feature, for example, using a feature flag will be the way to go. If, on the other hand, a project has little overlap with the work of other teams, using a feature branch is a great option that won’t cause you much trouble. Also, it gives you the liberty of introducing temporary instabilities. When choosing one of these two options, just be aware of the various advantages and drawbacks.
Regardless of the option you choose, keep in mind that it’s not ideal to leave either a feature branch or a feature flag open for too long. So still try to keep the scope of your projects as small as possible.
Additionally, if you decide to make use of either a feature branch or a feature flag, be sure to keep your code inside the branch or flag up to date as much as you can. That way you maintain compatibility with the rest of the codebase and avoid nasty surprises once you've finished your shiny new feature and want to ship it!