Feature Flagging: Fully Standardized with OpenFeature

31.01.2024โ€ขTom Trapp
Cloud feature-flagging openfeature Cloud native Cloud Native Computing Foundation Developer Experience Framework Open source How-to

Banner

In this TechUp, we aim to answer the following questions:

  • What is feature flagging? โ“
  • What is OpenFeature? โ“
  • Why is OpenFeature now CNCF incubating? ๐Ÿค”
  • Is it any good? ๐Ÿคทโ€

Feature Flagging? What is it?

Feature flagging is a technique in the software development process where features are controlled via configuration flags to change their visibility and behavior without re-deploying the code. This enables flexible release control, easy A/B testing, and improved error handling in production environments.

In theory, deployments can be carried out without risk, as new features are initially deactivated, for example. In a decentralized tool, the so-called feature flag provider, these features can then be activated generally or based on context-dependent criteria such as user, device, or location. This makes it possible to roll out features only for specific users, i.e., to run blue-green or canary releases. This architecture also allows features to be controlled across multiple services of different technologies.

A simple diagram to show the feature flag architecture

The image above shows that the feature flag provider controls the features of our application. Calls are made to the feature flag provider at runtime. It is also nice to see that the feature flag provider can decide whether to activate a feature or not based on additional information such as location or device.

Thus, we have answered our first question: Feature flagging is a technique to activate or deactivate features in software products.

OpenFeature

And OpenFeature is now a cool feature flag provider? No!

OpenFeature is an open standard and a framework for implementing feature flagging in software products. It standardizes the integration into individual software projects as well as the communication to the common feature flag providers.

The first commit was in February 2022, in the same year OpenFeature was proposed and accepted for the CNCF as a sandbox project. OpenFeature is primarily written in Python.

This allows us to use different feature flag providers without having to adapt our software, as we can use the standardized OpenFeature SDK.

Currently, the following SDKs are available:

  • Java
  • Node-js
  • .NET
  • Go
  • Python
  • PHP
  • Android
  • iOS
  • Web (JavaScript)

The following image shows the interaction between the OpenFeature SDK and the OpenFeature Provider as part of the Open Feature Flagging Client.

A simple diagram to show the open feature architecture

And now we can answer the second question! OpenFeature is an open standard and a framework/SDK for implementing feature flagging in software products.

Incubating

OpenFeature is now CNCF incubating.

OpenFeature hits the CNCF nerve exactly, as it offers an open and standardized solution for a problem that occurs again and again in the cloud-native world. It is vendor-neutral and allows the use of different feature flag providers without having to adapt our software, as we can use the standardized OpenFeature SDK.

And so we can answer the third question: OpenFeature is now CNCF incubating because it offers an open and standardized solution with many integration possibilities for a recurring problem.

Hands-on

Now let’s try out OpenFeature!

We use the Getting Started, which can be found here.

The project starts a NodeJS project for us, which we can control via API calls.

To play around with the project a bit, I created a fork, which you can find here on GitHub.

Installation

1
2
3
git clone https://github.com/b-nova-techhub/five-minutes-to-feature-flags/tree/main && \
  cd five-minutes-to-feature-flags && \
  npm install

Now our project is checked out and ready to go!

Hello World

Then we can execute the pre-built examples one after the other and examine the result using curl. First, we start our server:

1
node 01_vanilla.js

In a new shell, we can then make the call:

1
curl http://localhost:3333

And our service works, we get “Hello, world from b-nova!” back. ๐ŸŽ‰

In this example, we haven’t used any feature flags yet.

Full Fledged Example

We skip the other examples and jump directly to the last example, which shows us the integration of OpenFeature including provider and context-dependent evaluation. Of course, you can find the other examples in the documentation at OpenFeature.

Let’s take a look at the code and get to know OpenFeature!

The goal of the program is to display the cow for a specific user! ๐Ÿฎ

Below is the file 05_openfeature_with_targeting.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import express from "express";
import Router from "express-promise-router";
import cowsay from "cowsay";
import { OpenFeature, InMemoryProvider } from "@openfeature/server-sdk";

const app = express();
const routes = Router();
app.use((_, res, next) => {
  res.setHeader("content-type", "text/plain");
  next();
}, routes);

// A: create the OpenFeature client
const featureFlags = OpenFeature.getClient();

// B: The FLAG_CONFIGURATION for the InMemoryProvider
const FLAG_CONFIGURATION = {
  'with-cows': {
    variants: {
      on: true,
      off: false
    },
    disabled: false,
    defaultVariant: "off",
    contextEvaluator: (context) => {
      if (context.user === "Tom") {
        return "on";
      }
      return "off";
    },
  }
};

// C: Initialize a Provider
const featureFlagProvider = new InMemoryProvider(FLAG_CONFIGURATION);

// D: Set the Provider onto the OpenFeature Client
OpenFeature.setProvider(featureFlagProvider);

routes.get("/", async (req, res) => {

  // E: create the context to be sent to the provider
  const context = {
    user: req.get("x-user")
  };

  // F: call the OpenFeature client at requesttime to evaluate the flag, with default value and the context
  const withCows = await featureFlags.getBooleanValue("with-cows", false, context);
  if (withCows) {
    res.send(cowsay.say({ text: "Hello, world from b-nova!" }));
  } else {
    res.send("Hello, world from b-nova!");
  }
});

app.listen(3333, () => {
  console.log("Server running at http://localhost:3333");
});

What happens in this file? We create an Express server that listens on port 3333. This server has a route that is called when we make a GET request to the path “/”. Within the function of the route, we evaluate a feature flag and return different responses depending on it.

Now let’s dive deeper into the code and look at the relevant lines (see the comments in the code).

  • A: We create an OpenFeature client, which provides us with the functionality to evaluate feature flags.

  • B: We create a FLAG_CONFIGURATION, which we pass to the provider. In this example, we only have one feature flag, which we call “with-cows”.

    • This feature flag has two variants, “on” and “off”, the default value is “off”.
    • With the contextEvaluator, we define that if the user is “Tom”, the feature flag should be “on”, otherwise “off”.
    • The contextEvaluator allows us to activate a new feature only for specific users and thus roll it out granularly.
  • C: We create a provider, in this case an InMemoryProvider, which delivers the feature flags from the FLAG_CONFIGURATION.

    • Here we could connect other providers, such as a flagd provider. We don’t have to change anything in our code because we use the OpenFeature client.
    • With other providers, the configuration (line B) would then no longer be in the code, but decentralized in another service.
  • D: We set the provider on the OpenFeature client.

  • E: We create a context, which is passed to the provider. In this example, the context is a user, which is passed in the header of the request.

    • We can extend this context as we like, for example, to also include the device or location.
  • F: We call the OpenFeature client and evaluate the feature flag “with-cows” with the default value “false” and the context.

    • The OpenFeature client now calls the provider and asks for the feature flag “with-cows”.
    • The provider now looks in the configuration to see if the feature flag “with-cows” exists.
    • If yes, the contextEvaluator is called and the value is returned.
    • If no, the default value is returned.
    • The OpenFeature client now returns the value that was returned by the provider.

Now we want to test our code! We start our server with node 05_openfeature_with_targeting.js. Then we use curl to make some calls:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
curl http://localhost:3333

# Response: Hello, world from b-nova!

curl http://localhost:3333 -H "x-user: John"

# Response: Hello, world from b-nova!

curl http://localhost:3333 -H "x-user: Tom"

# Response:  

 ___________________________
< Hello, world from b-nova! >
 ---------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||% 

And there it is, our cow! ๐Ÿฎ

Our OpenFeature client calls our InMemoryProvider with a context on each request and thus evaluates the feature flag. This allows us to control that only I (Tom) see the cow and everyone else does not.

At this point, it is important to mention that we only use the InMemoryProvider for this example. Thus, the FlagConfiguration is in the code and not decentralized in another service. This also means that the server has to be restarted every time the flag is changed. In a real-world scenario, we would use a decentralized provider like flagd, which removes the configuration from the code and thus enables changes at runtime. Our server would then make a call via API to the provider on each request, and the configured feature flags would then be evaluated there. This gives us a clear separation between code and configuration.

Conclusion

I have to admit, at first I was skeptical, why do you need feature flagging, why do you need OpenFeature. But it quickly becomes clear that this makes it easy to implement canary releases, A/B tests, and much more without duplicating the entire infrastructure and architecture. This saves costs and makes the software more flexible, as features can be deployed deactivated and are “hidden” in the code. The advantages certainly come into play even better when you operate feature flagging at scale and can roll out or control features simultaneously across multiple services.

OpenFeatures should definitely be part of every feature flagging, as it offers a standardized and independent implementation.

Here we can answer the last question: Yes, OpenFeature is good! ๐Ÿš€

Outlook

What’s next? I could imagine another TechUp where we connect a decentralized provider like flagd and thus remove the configuration from the code. It would also be exciting to take a closer look at the Kubernetes OpenFeature Operator and define feature flags as CRDs.

With these two points, we are then approaching an enterprise-ready feature flagging system. ๐Ÿš€

This techup has been translated automatically by Gemini

Tom Trapp

Tom Trapp โ€“ Problemlรถser, Innovator, Sportler. Am liebsten feilt Tom den ganzen Tag an der moderner Software und legt viel Wert auf objektiv sauberen, leanen Code.