Here's how you can start using feature flags today published 11/30/2019 | 7 min read

Welcome everyone to a new devspedia story. Today I'll be talking about feature flag concept/practice in software development.



Feature Toggles (often also refered to as Feature Flags) are a powerful technique, allowing teams to modify system behavior without changing code — Martin Folwer

What is a feature flag?

A feature flag is a technique to turn on/off a feature at any time without having to maintain separate branches for each feature, and then once merged, is on by default.

It'll basically allow you to ship a product with a feature switched off, then switch it on again any time in the future.



How it works?

Take this minimal example into consideration:

  
const sendSMSConfirmation = false;
function sendOrderConfirmation() {
  sendEmailConfirmation();
  if(sendSMSConfirmation) {
    sendSMSConfirmation();
  }
}

Simple example showing feature flags usage

In this example, we have a toggle/bool to tell us whether or not to send SMS messages after sending email confirmation. It's false or off by default.

Whenever business requires to switch on this feature at any time, we can easily set sendSMSConfirmation to true. Now it works.



Ways to implement feature flags:

There are many ways you can implement feature flags, most common are the following:

  1. By decoupling decision points from decision logic.
  2. Inversion of Control.

Let's briefly explain these 2 main techniques.

1- Decoupling decision points from decision logic

This means the feature flags will have absolutely nothing to do with the rest of your application logic, they are just bools, and then you can use them however you wish, take this example for better illustration:

  
const features = getFeatureFlags();
  
function sendOrderConfirmation() {
    sendEmailConfirmation();
    if(features.TRANSACTIONAL_SMS) {
          sendSMSConfirmation();
    }
}

Here, we can freely use this feature flag anywhere in our app logic, simply by doing this conditional.

Now, given we have a more broader feature flag: TRANSACTIONAL_SMS which maybe indicate a set of features, this direct usage is introducing few challenges:

  1. What if we want to rollout this feature to a particular set of users?
  2. What if we want to turn on some parts of TRANSACTIONAL_SMS but without this sendSMSConfirmation part?

Now, there's a way to solve this be decoupling the decision point of sending SMS confirmation from the actual logic of sendOrderConfirmation. Take this example:

  
function orderConfirmationDecisions(features){
  return {
    sendSMSConfirmation(subscription){
      return features.TRANSACTIONAL_SMS && subscription.isPaid;
    };
  };
};

Order confirmation decisions

Now in this decisions file, we have a decision maker for order confirmations, here we can add more login to this sendSMSConfirmation decision (ex: by checking the flag and subscription.isPaid).

Now let's see the refactored sendSMSConfirmation function:

  
const features = getFeatureFlags();
const decisions = orderConfirmationDecisions(features); // from prev file
const subscription = getUserSubscriptionSomehow();

function sendOrderConfirmation() {
  sendEmailConfirmation();
  if(decisions.sendOrderConfirmation(subscription)) {
    sendSMSConfirmation();
  }
}

Refactored senOrderConfirmation function, now with decision decouple

2- Inversion of Control.

In the above example we've introduced a new function that's responsible for taking order confirmation decisions, which is a dependency for sendOrderConfirmation, also decisions must be aware of flagging.

This way, and by expanding to other system modules, will add extra layer of dependency to all modules logic, make testing harder, and will create a global feature flags dependency.

In Software design there's a technique called Inversion of Control, which we can use here to solve this problem.

Take the example below for better illustration:

  
function sendOrderConfirmation(config) {
    sendEmailConfirmation();
    if(config.sendSMSConfirmation)) {
          sendSMSConfirmation();
    }
}

Now in order to use this sendOrderConfirmation function, we can have it go through a feature flagging factory, which will give the function the necessary config with all decision pre-made at run-time.

  
const sendOrderConfirmation = getSendOrderConfirmationFunction();

function createFeatureAwareFactoryBasedOn(decisions){
  return {
    sendOrderConfirmation() {
      const config = {
        sendSMSConfirmation: decisions.sendOrderConfirmation(),
      };
      return sendOrderConfirmation(config);
    },
  };
}

What we did is basically adding a layer between the sendOrderConfirmation function, and between the decisions it should take, through constructing a config for this function.

This config can be not only the decisions, but can also include extra logic outside of the scope of feature flags.

How to configure the feature flags:

Now figured out how to implement feature flags in our application logic, but how to actually build feature flags. There are few ways.

1- Maintaining config files:

This is basically by having a file like so:

  
{
  "PARENT_SET_OF_FEATURES": {
    "TRANSACTIONAL_SMS": true
  },
  "ANOTHER_FLAG": false
}

feature flags config file.

This is probably the simplest way, but it has it's drawbacks specially in larger applications, it becomes very difficult to guarantee consistency of feature flags across a large number of distributed servers.

2- Use the application database

This will guarantee consistency somehow, and is a natural place to keep your feature flags, however, nowadays there are better ways to manage feature flags, there are hierarchical key-value stores that can be used.

3- Use these tools if you have a distributed cluster

If you have a distributed cluster, you can use tools like Consul, Zookeeper, or etcd.

Consul by HashiCorp
Consul is a service networking solution to connect and secure services across any runtime platform and public or private cloud
etcd
A distributed, reliable key-value store for the most critical data of a distributed system

These services from a distributed cluster, will allow you to have a shared source of environmental configurations for all of your cluster nodes. This guarantees that changes in your system environment configurations will get distributed on all node everywhere.

Consul provides a web interface where you can manage these configurations easily, however in most cases developers will create a administration app for managing these feature flags.

The final bit:

There are many other details I didn't yet cover in this story, like for example, how to override feature flags value per environment (some real world cases require you to have different config for a particular set of your app deployments based on geographical or other constraints). There are also some techniques to override feature flags based on the user's HTTP request, and more.  But I'd like you to go ahead, do more research and dive deeper, knowledge about this topic is endless everywhere.

Resources:

That's all, thanks for reading devspedia, I love you, and see you the next time :)



You may also like reading: