Understand Feature Flags by Practice with Unleash
• 7 minute read
feature flags, django, nextjs
Table of contents
When you start a task, it could be part of something big (like an epic) that will only be fully deployed after some weeks or months. So instead of delivering a massive pull request with many files to review, you can put your code to be reviewed progressively without disrupting existing services. This can be done by using feature flags. Shipping fast is vital, though care with stability and quality is also critical.
Feature flags typically are literally IF conditionals in your code
We are talking about IF-ELSE statements in your code. Let's see a real example. It enables a ModelViewSet
(check it on the DRF website) to only proceed with the request if the feature ENABLE_PROFILE_API
is enabled:
class ProfileViewSet(viewsets.ModelViewSet):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
filter_backends = [filters.SearchFilter]
search_fields = ["username", "sex", "mail"]
def initial(self, request, *args, **kwargs):
"""
https://www.django-rest-framework.org/api-guide/views/#initialself-request-args-kwargs
"""
enable_profile_api = client.is_enabled("ENABLE_PROFILE_API")
if not enable_profile_api:
raise Http404("Not available")
super().initial(request, *args, **kwargs)
The sample feature flag above is provided by a client. Still, you can retrieve it from an environment variable or your database, considering more straightforward solutions.
Usually, that's what you'll see from a feature flag, but you can also use more sophisticated flags. For example, instead of storing boolean values, they can also store strings. You can also configure to activate them based on who is accessing your service. Of course, these and other features depend on your approach. Let's see some of them.
Possible approaches
Let's see some techniques, their advantages, and drawbacks:
- Environment variables: Using Kubernetes, you can apply env variables through ConfigMaps or Secrets. The problem with it, though, is how they are applied. A new deployment is necessary every time you update them. It's a decentralized solution. If two different applications use the same flag name, both will need a deployment. Imagine you find a bug after the deployment; you'll have to wait until the new deployment occurs, so the service can be healthy again.
- Database: If your application has a database, you can create a table that stores the toggles. A new database query is necessary for every business flow that requires toggle evaluation. This approach is not a problem, but this method limits the feature flag for backend applications. It's also a decentralized solution. The main benefit of this process is that it does not require a new deployment.
- Centralized solution: You develop your own feature toggle management service. But that famous question appears: Why reinvent the wheel?
Talking about a centralized solution, have you ever heard of Unleash?
Unleash
It has excellent documentation explaining how it works, how is its architecture options, pricing, and many other things. The primary reason I've chosen it to comprehend a centralized solution it's because it is open-source. Let's explore how it works in a Python/Django project and a JavaScript/Next.js project. First of all, access this folder and execute the command:
docker-compose up
When everything is up, access the page http://localhost:4242/
and use the credential admin:unleash4all
. Next, click on the project default
.
On its upper right, click on the import button.
A panel will appear. Click on select file
and then use 2023-04-29T21_20_20.516Z-export.json
from the iac
folder.
Just follow the wizard until you conclude the import process. Then, you'll see 9 feature toggles:
Python/Django project
Access the address http://localhost:8000/
, and you may see this:
I said may because if you press F5, you can see some variations. For example, check out what happened in my case:
If you access the project default
on Unleash, you'll see the column seen
with values.
By the way, each F5 may change how you see the website. If you want to persist the configuration, you can use stickiness.
Changing stickiness
Click on the feature toggle PROFILE_MANAGEMENT_BUTTON_SCHEME
and then click on the tab variants (click here for a shortcut). Next, click on edit variants
.
Go to the bottom of the page. Change the stickiness option from random to default. We'll see a consistent experiment if you press F5 on the page.
When the change is committed and available for use (it takes 5 or 10 seconds), if you press F5 many times, you'll see the text is sometimes changing, not the button scheme anymore.
Feature toggle that stores JSON
The feature flag TEXT_PRESENTATION
is not a classic ON/OFF. Actually, it stores a JSON.
logger.info("Using the button scheme %s with value %s", button_scheme_name, button_scheme_value)
text_presentation = client.get_variant("TEXT_PRESENTATION", context=user_context)
if text_presentation["enabled"]:
text_presentation_name = text_presentation["name"]
text_presentation_value = json.loads(text_presentation["payload"]["value"])
else:
text_presentation_name = "FALLBACK"
text_presentation_value = {
"title": "Hello there 😄!",
"subTitle": "Change how this app behave by changing the feature toggle tool ⚒",
"profileTitle": "Registered profiles",
}
logger.info("Using the text presentation %s with value %s", text_presentation_name, text_presentation_value)
Feature toggle for specific users
Access the toggle GAME_SHARK_MODE
and look at its deployment strategy:
It's a dedicated toggle only ON if the user has the ID d821cbc0-2e4d-49fc-a5b4-990eb991beec
. To configure a user ID, we do this on the backend side:
def index(request):
user_id_pc = "40956364-e486-4d8e-b35e-60660721f467"
user_id_mobile = "d821cbc0-2e4d-49fc-a5b4-990eb991beec"
user_id = user_id_pc if request.user_agent.is_pc else user_id_mobile
user_context = {
"userId": user_id,
# Browser family can be `chrome` or `firefox`
"browser": request.user_agent.browser.family.lower(),
}
# ...
game_shark_mode = client.is_enabled("GAME_SHARK_MODE", context=user_context)
If you change the access mode to a mobile one, then you'll see a new behavior:
There are many activation strategies that you can test:
JavaScript/Next.js project
It's the same project as the Python one regarding behavior, but using another SDK and not including some toggles, such as ENABLE_PROFILE_API
and ENABLE_PROFILE_ADMIN
. Try it yourself 😄.
What if the centralized server is off?
You can circumvent an outage of the feature management server by doing two things in the case you're using Unleash:
- Unleash Edge: It sits between the Unleash API and your SDKs and provides a cached read-replica of your Unleash instance. Currently, it has modes edge and offline. I recommend looking at its docs.
- Client-side cache: Unleash has an extensive list of client SDKs; it seems all have cache strategies. For instance, the JavaScript browser SDK offers the option bootstrap. That means you can download all your configuration from a CDN and use it while your edge or central server is off.
Conclusion
A feature flag is necessary if you want to take a step forward for frequent releases. It comes with some impacts, though. Now you have to do more tests to check how a particular business rule behaves if a toggle XYZ and ACME are activated or how it acts if only toggle XYZ is activated, for example. This means your tests are more expensive now. Beyond that, a feature toggle governance is also required. What if a team left behind a toggle that should have been removed? There are types of toggles; one may be of type permission (no expected lifetime), and the other may be of type experiment (expected lifetime of 40 days). Well, it's a game of tradeoffs.
See everything we did here on GitHub.
Posted listening to All Apologies, Nirvana 🎶.