9 September 2021

Replacing long-running branches with feature flags

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.

Rico Sta. Cruz @rstacruz

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.

Merge conflict

What’s wrong with Git branches?

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:

Alternative: feature flags

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).

if (process.env.NODE_ENV === 'production') {
  // ...
}

Level 0: Hiding UI in production

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.

client_side_example.tsx
const isProduction =
  process.env.NODE_ENV === 'production'

<div>
  {isProduction ? null : <button>Show lyrics</button>}
</div>
server_side_example.rb
<% if Rails.env.production? %>
  <button>Show lyrics</button>
<% end %>

Caveats to note

✨ Next, let’s organise mutiple flags with configuration.

Level 1: Per-feature 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.

.env
LYRICS_VIEW_ENABLED=0
TRENDING_PAGE_ENABLED=0
.env.development
LYRICS_VIEW_ENABLED=1
TRENDING_PAGE_ENABLED=1
Different environments can each have configuration that determines what features are on.

Caveats to note

Level 2: Suppressing the routes

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.

routes.js
const isProduction =
  process.env.NODE_ENV === 'production'

if (!isProduction) {
  router.get('/browse/trending', browseByTrending)
}
A JavaScript example of how URL routes can be turned off based on environment variables.

Caveats to note

✨ Next, let’s try enabling the feature for a limited set of users.

Level 3: User flags

Features can be limited to certain users such as “admin” users. This allows the team to test out new features in production.

const user = getCurrentUser()

<% if (user.admin) { %>
  <a href='/trending'>View trending albums</a>
<% } %>
In this example, the “View trending albums” button is only shown to admin users.

Caveats to note

✨ Next, let’s look at a possible solution that might remove the need for state storage.

Level 4: Cookies

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.

<% if (req.cookies['trending-enabled'] === '1') { %>
  <a href='/trending'>View trending albums</a>
<% } %>
This example shows a link only for users with a certain cookie.

Caveats to note

✨ Next, let’s try to make this a bit more user-friendly.

Level 5: UI for setting cookies

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
Microsoft Edge features an about:flags page for experimental features.

Caveats to note

✨ Next, let’s try to prevent unauthorised users from enabling feature flags.

Level 6: Signed cookies

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.

Level 7: Managed feature flag service

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.

Thanks for reading! I'm Rico Sta Cruz, I write about web development and more. Subscribe to my newsletter!