Deployment with feature flags
In your typical enterprise project there probably is everything aplenty and you rarely have to downsize to match the available resources. Well - until you actual have to do that. What is when your team consists of only a few people and you cannot afford time and money-wise to maintain the objectively best approach of the dev-test-stage-prod quadruplet?
So in this blog we are going to shed some light on the feature flags idea and cover things that actually surprised me forced me to recalculate my mental model of feature flags as just stupid booleans on a simple, but effective use-case with the help of Unleash.
I can highly recommend the book The Feature Flag Book [featflag], which helped me along the road and provided me with some funny rollercoaster-moments, especially at the end. |
So hold your breath! (Breathe..)
What are feature flags? &
Like I’ve said before, when I started this journey feature flags for me were just mere booleans, that can be set or unset to enable resp. disable sections of the code:
if featAIsEnabled {
doSomeThingA()
} else {
doSomeOtherThingB()
}
This is true for the most basic part of them and can be done without much work, if you can live with the additional time to compile, release and deploy the new versions and of course downtime, which might bite away a bit of your SLA error budget.
Unfortunately this also burdens the team with handling of many more different artifacts, code bases, pull requests and also increases the cognitive complexity of the additional code paths.
Testing won’t obviously be easier that way, too.
Check flags per runtime &
I can’t really explain why, but I never thought about checking feature flags per runtime of the application which is really an effective way to handle the above problems.
When you can call an endpoint that has some information about which feature is activated this can alleviate the problems from above and reduce the number of artifacts to a hopefully manageable level.
This requires some careful consideration of the requests, error handling and caching, because it creates another level of coupling and single-point-of-failure, but this trade-off might worth it.
Once we have everything in place - what else can be done with feature flags?
Rules and variants &
Let us reconsider our original idea of simple booleans and replace it with a more some kind of nifty rule:
if featAIsEnabled {
if "127.0.0.1" == remoteIP { (1)
doSomeThingA1()
} else {
doSomeThingA2()
}
} else {
doSomeOtherThingB()
}
1 | Stupid; but you probably get the gist of it. |
This way rules can be used to add conditions to the flags and e.g. enable specific behaviour just for small and targeted audience.
Since we don’t want to cycle back to the original problem of the different builds and handle this outside of our actual application:
variant := featAIsEnabledFor("127.0.1.1") (1)
switch variant { (2)
case "a1":
doSomeThingA1()
break
case "a2":
doSomeThingA2()
break
default:
doSomeOtherThingB()
}
1 | Include some information about the client to feed into a rule |
2 | The next path is dependent on the return value (or variant) |
So far we spoke about a theoretical endpoint that can do all the magic for us, time to turn that from a theoretical into a practical. I am absolutely certain we can build the backend endpoint on our own, but let me suggest a different strategy this time:
Unleash unleashed &
Unleash provides us with all the technical means we’ve identified so far and packs it into a nice management console. Setting it up is just a quick flex of your Docker Podman muscles and should be running in no time:
The UI of the console is pretty much self-explanatory and a new feature flag can be created like this:
![feature flag](/assets/images/deployment_with_feature_flags/feature_flag.png)
Once this is set we can update our application and include the specific SDK and request:
ctx := unleashContext.Context{ (1)
UserId: "1",
SessionId: "some-session-id",
RemoteAddress: context.RemoteIP(),
}
if unleash.IsEnabled("feat.CheckBadwords", unleash.WithContext(ctx)) { (2)
if containsBadword(todo.Title) {
context.JSON(http.StatusExpectationFailed, gin.H{"error": "Title contains badword"})
return
}
}
1 | As before we provide a bit background infos |
2 | And ask for the state of the actual flag - which always evaluate to false if non-existent |
With our changes up and running we can head back to our fancy management console and enable our new feature flag:
![feature flag enabled](/assets/images/deployment_with_feature_flags/feature_flag_enabled.png)
Enabling the flags also enables the new behaviour and forbids todo with a crappy name, after a bit of propagating time.
The showcases uses both intervals unleash.WithRefreshInterval(1*time.Second) and
unleash.WithMetricsInterval(1*time.Second) at non-productive-ready values, to make demonstration
easier, but this strictly requires some fine-tuning. The default of 60s is probably a good
value to start.
|
I recently discovered slumber and greatly fell in love with it, so instead of the typical curl-output here a screenshot of slumber in action:
![slumber](/assets/images/deployment_with_feature_flags/slumber.png)
So far we haven’t covered the rules and variants idea and this and more is certainly possible
with unleash.
There are many options to choose from, so how about a gradual rollout just for the user with the
id 1
which happens to be our sole user?
![feature flag strategy](/assets/images/deployment_with_feature_flags/feature_flag_strategy.png)
At the bottom you can see another bonus of using a full-fledged feature flag system: We get exposure and request stats for free!
It is probably easy to see why this is a nice gimmick for the technical folks and also for the targeted users of management consoles, but what have we actually won here?
Deployment vs release &
From my opinion the real benefit is a deployment and a release are disjunct from each other. We can do one without the other and therefore can easily deploy versions, test a new feature and disable it again when something goes wrong.
This is similar to the benefits of A/B testing or canary rollouts, which require actual deployments and a concept to avoid downtimes.
And the option to target specific user groups based on information of e.g. the session might allow to make four stages obsolete. Additionally this might increase the trust in deployments to production, because there is always a way to disable certain features and this might also be done by non-tech-savvy folks.
Conclusion &
Like every fancy new idea this might sound like Maslow’s golden hammer and there are many more things that could be done with feature flags, but not necessarily should be done with it. In the aforementioned book [featflag] the author kind of lost me with the idea to do authorization with feature flags, but gladly put that into context in the later chapters.
This solution like every other elses harbors some trade-offs and every architectural change should be done after proper consideration. When this works our for you and your organization it might speed up the development time and also reduces a bit of the drag to manage all the different stages, so it is up to you to give it try.
All examples can be found here:
Bibliography &
-
[featflag] Ben Nadel, Feature Flags Book, https://featureflagsbook.com