Pros and cons of different feature flags table designs
Learn the pros and cons of structuring your features flags horizontally or vertically in your feature flags table with examples.
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
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.
Take this minimal example into consideration:
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.
There are many ways you can implement feature flags, most common are the following:
Let's briefly explain these 2 main techniques.
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:
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:
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
function:sendSMSConfirmation
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.
Now figured out how to implement feature flags in our application logic, but how to actually build feature flags. There are few ways.
This is basically by having a file like so:
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.
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.
If you have a distributed cluster, you can use tools like Consul, Zookeeper, or etcd.
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.
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 :)