Can we do better than Gitflow? Maybe we don't need to keep branches around anymore.
Git branches are often used to write features that take a long time to write. I’m starting to think that they may not be the ideal solution.
A branch might be worked on for weeks then finally merged to the main branch when it’s done. Almost every team I’ve worked on runs into the same issues with long-running branches:
They get out-of-sync. Branches will require regularly merging in changes from main. Devs tend to avoid this chore, making things worse.
They’re impossible to review. Long-running branches often live as pull requests with thousands of lines.
They’re difficult to revert. If any issues arise after merging into production, features may need to be “undeployed.” This is easy with git revert until more code piles up to make it impractical.
What if WIP features can be merged into production? Maybe it can. Feature code in the main branch doesn’t have to be made available in production. Blocks of code can always be turned off with conditional statements.
These conditionals are often called feature flags or feature toggles. I’ll list down a few ways I know of implementing feature flags, starting with the simplest (level 0) to the more advanced (level 7).
A feature’s UI can be disabled in production with an if condition. This would allow the app to be deployed even if some features aren’t complete yet.
Pro: The feature works in development, but not in production. This means the feature can be merged into the main branch and deployed without affecting users.
Con: Testing can be difficult. This makes the new features available in tests, and toggling these in unit tests means having to hijack environment variables somehow.
Con: Features also show up in dev mode. Config-based checks are often preferred over environment checks for this reason.
Next, let’s organise multiple flags with configuration.
Having one switch per feature can be more flexible, not to mention more organised. Rather than relying on NODE_ENV, each environment can have a list of flags that are enabled for it. Common conventions include environment variables (shown below) or YAML configuration files.
Pro: Easy to see what features are available, and they can be adjusted per feature (eg, to deploy one feature to production).
Con: URL’s can still be guessed. For example, a trending page may be available in /browse/trending
which others can guess. While it’s often enough to hide links to new features, some new URL routes may also need to be hidden.
URL routes can be disabled in production in the same way. While hiding links will make the feature invisible, that doesn’t prevent users from guessing the URL of new features.
Pro: Same pros as above (level 1) with none of its cons.
Con: Features aren’t available in production. This is often what we want at first, but at some point it would be nice for the internal team to try the feature out in production.
Next, let’s try enabling the feature for a limited set of users.
Features can be limited to certain users such as “admin” users. This allows the team to test out new features in production.
Pro: If your app already has a functionality to discern admin users, this can be an easy win.
Con: User accounts are required. This makes this method not suitable for features not requiring users to sign in, or sites without authentication.
Con: Requires database storage. While it’s easy for some apps to store metadata for a user (eg, an admin
flag), this might not always be the case.
Next, let’s look at a possible solution that might remove the need for state storage.
Features can be restricted in production using special cookies. Unlike the user-based approach, this allows testing of features that don’t require signing in.
Pro: Internal team members will be able to try out the feature in production before real users see it.
Con: May be difficult to enable. Developers may know their way around adding new cookies, but non-technical people may need something more user-friendly.
Con: Can be prone to tampering. Some users will be able enable features if the cookie names leak out.
Next, let’s try to make this a bit more user-friendly.
A simple page can be used to list all cookie-based flags and provide a UI to enable and disable them.
Major browsers today have a secret page to allow enabling experimental features. Something like this can also be done for web apps, possibly hidden by authentication.
about:flags
page for experimental features.Pro: Internal team members will be able to try out the feature in production before real users see it.
Con: Can be prone to tampering. Clever users will be able enable features if the cookie names leak out.
Next, let’s try to prevent unauthorised users from enabling feature flags.
Cookie-based feature flags can be cryptographically-signed to avoid tampering. This is done in a number of ways depending on the framework being used. Rails’s cookies.signed
appends the payload with an HMAC signature, similar to JWT tokens.
A number of third-party providers offer feature flag management. This brings features like to allow rolling out features by location, by AB split testing, and more. These can be used to make WIP features only available to internal team users, too.
Git branches may not be the ideal solution for long-running features. Branches often become out-of-sync, are difficult to review, and can be challenging to revert if issues arise. An alternative solution is to use feature flags or feature toggles. By using conditional statements, features can be merged into the main branch without being made available in production.
Have you used feature flags below? What was your experience like? Let me know in the comments below!
I am a web developer helping make the world a better place through JavaScript, Ruby, and UI design. I write articles like these often. If you'd like to stay in touch, subscribe to my list.