Feature-Flagging: Vollständig standardisiert mit OpenFeature

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

Banner

Folgende Fragen wollen wir in diesem TechUp beantworten:

  • Was ist Feature-Flagging? ❓
  • Was ist OpenFeature? ❓
  • Wieso ist OpenFeature nun CNCF incubating? 🤔
  • Taugt das was? 🤷‍

Feature-Flagging? Was ist das?

Feature Flagging ist eine Technik im Softwareentwicklungsprozess, bei der Funktionen über Konfigurationsflags gesteuert werden, um deren Sichtbarkeit und Verhalten ohne Neuauslieferung des Codes zu ändern. Dies ermöglicht eine flexible Veröffentlichungssteuerung, einfache A/B-Tests und eine verbesserte Fehlerbehandlung in Produktionsumgebungen.

In der Theorie können so Deployments ohne Risiko durchgeführt werden, da neue Features beispielsweise erstmal deaktiviert sind. In einem dezentralen Tool, dem sogenannten Feature-Flag-Provider, können diese Features dann generell oder anhand von kontextabhängigen Kriterien wie Benutzer, Gerät oder Standort aktiviert werden. So ist es möglich, Features nur für bestimmte User auszurollen, also Blue Green bzw. Canary Releases zu fahren. Diese Architektur erlaubt es ausserdem, Features hinweg über mehrere Services unterschiedlicher Technologien zu steuern.

A simple diagram to show the feature flag architecture

Im oberen Bild ist zu sehen, dass der Feature-Flag-Provider die Features unsere Application steuert. Dabei werden zur Laufzeit Calls gegen den Feature Flag Provider gemacht. Schön zu sehen ist ebenfalls, dass der Feature Flag Provider anhand von zusätzlichen Informationen wie dem Standort oder dem Gerät entscheiden kann, ob ein Feature aktiviert werden soll oder nicht.

Somit haben wir unsere erste Frage beantwortet: Feature-Flagging ist eine Technik, um Features in Software-Produkten zu aktivieren oder zu deaktivieren.

OpenFeature

Und OpenFeature ist nun ein cooler Feature-Flag-Provider? Nein!

OpenFeature ist ein offener Standard sowie ein Framework, um Feature-Flagging in Software-Produkten zu implementieren. Es standardisiert die Integration in den einzelnen Softwareprojekten sowie die Kommunikation hin zu den gängigen Feature-Flag-Providern.

Der erste Commit war im Februar 2022, im selben Jahr wurden OpenFeature für die CNCF als Sandbox Projekt vorgeschlagen und angenommen. OpenFeature ist primär in Python geschrieben.

Dies erlaubt es uns, verschiedene Feature-Flag-Provider zu nutzen, ohne dass wir unsere Software anpassen müssen, da wir die standardisierte OpenFeature SDK nutzen können.

Aktuell sind folgende SDKs verfügbar:

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

Im nachfolgenden Bild ist das Zusammenspiel zwischen OpenFeature SDK und dem OpenFeature Provider als Teil des Open Feature Flagging Clients zu sehen.

A simple diagram to show the open feature architecture

Und nun können wir die zweite Frage beantworten! OpenFeature ist ein offener Standard sowie ein Framework/SDK, um Feature-Flagging in Software-Produkten zu implementieren.

Incubating

OpenFeature ist nun CNCF incubating.

OpenFeature trifft genau den CNCF-Nerv, da es eine offene und standardisierte Lösung für ein Problem bietet, welches in der Cloud Native Welt immer wieder auftritt. Es ist vendor neutral und erlaubt es, verschiedene Feature-Flag-Provider zu nutzen, ohne dass wir unsere Software anpassen müssen, da wir die standardisierte OpenFeature SDK nutzen können.

Und so können wir die dritte Frage beantworten: OpenFeature ist nun CNCF incubating, da es eine offene und standardisierte Lösung mit vielen Integrationsmöglichkeiten für ein wiederkehrendes Problem bietet.

Hands-on

Nun wollen wir OpenFeature ausprobieren!

Wir nutzen hierfür das Getting Started, welches hier zu finden ist.

Das Projekt startet uns ein NodeJS Projekt, welches wir via API Calls ansteuern können.

Um mit dem Projekt ein bisschen spielen zu können habe ich einen Fork erstellt, diesen findet ihr hier im 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

Nun ist unser Projekt ausgecheckt und ready to go!

Hello World

Anschliessend können wir die vorgefertigten Beispiele nacheinander ausführen und mittels curl das Ergebnis begutachten. Zuerst starten wir unseren Server:

1
node 01_vanilla.js

In einer neuen Shell können wir dann den Aufruf machen:

1
curl http://localhost:3333

Und unser Service funktioniert, wir bekommen “Hello, world from b-nova!” zurück. 🎉

In diesem Beispiel haben wir noch keine Feature Flags benutzt.

Full Fledged Example

Wir überspringen die anderen Beispiele und springen direkt zum letzten Beispiel, welches uns die Integration von OpenFeature inklusive Provider und kontextabhängiger Evaluation zeigt. Selbstverständlich findet ihr die anderen Beispiele in der Dokumentation bei OpenFeature.

Schauen wir uns den Code an und lernen OpenFeature kennen!

Ziel des Programms ist es, für einen bestimmten User die Kuh anzuzeigen! 🐮

Nachfolgend ist die Datei 05_openfeature_with_targeting.js zu sehen:

 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");
});

Was passiert in diesem File? Wir erstellen uns einen Express Server, welcher auf Port 3333 lauscht. Dieser Server hat eine Route, welche aufgerufen wird, wenn wir eine GET Anfrage auf den Pfad “/” machen. Innerhalb der Funktion der Route evaluieren wir ein Feature Flag und geben abhängig davon unterschiedliche Antworten zurück.

Tauchen wir nun mehr in den Code ein und schauen uns die relevanten Zeilen an (siehe die Kommentare im Code).

  • A: Wir erstellen uns einen OpenFeature Client, welcher uns die Funktionalität zur Verfügung stellt, um Feature Flags zu evaluieren.

  • B: Wir erstellen uns ein FLAG_CONFIGURATION, welches wir dem Provider übergeben. In diesem Beispiel haben wir nur ein Feature Flag, welches wir “with-cows” nennen.

    • Dieses Feature Flag hat zwei Varianten, “on” und “off”, der default Wert ist “off”.
    • Mit dem contextEvaluator definieren wir, dass wenn der User “Tom” ist, das Feature Flag “on” sein soll, ansonsten “off”.
    • Der contextEvaluator erlaubt es uns, ein neues Feature nur gezielt für bestimmte User zu aktivieren und so granular auszurollen.
  • C: Wir erstellen uns einen Provider, in diesem Fall einen InMemoryProvider, welcher die Feature Flags aus dem FLAG_CONFIGURATION ausliefert.

    • Hier könnten wir andere Provider anbinden, wie zum Beispiel einen flagd Provider. An unserem Code müssen wir nichts ändern, da wir den OpenFeature Client nutzen.
    • Bei anderen Providern wäre die Konfiguration (Line B) dann nicht mehr im Code, sondern dezentral in einem anderen Service.
  • D: Wir setzen den Provider auf den OpenFeature Client.

  • E: Wir erstellen uns einen Context, welcher an den Provider übergeben wird. In diesem Beispiel ist der Context ein User, welcher im Header der Anfrage mitgegeben wird.

    • Diesen Kontext können wir beliebig erweitern, um zum Beispiel auch das Gerät oder den Standort mitzugeben.
  • F: Wir rufen den OpenFeature Client auf und evaluieren das Feature Flag “with-cows” mit dem default Wert “false” und dem Context.

    • Der OpenFeature Client ruft nun den Provider auf und fragt nach dem Feature Flag “with-cows”.
    • Der Provider schaut nun in der Konfiguration nach, ob das Feature Flag “with-cows” existiert.
    • Wenn ja, wird der contextEvaluator aufgerufen und der Wert zurückgegeben.
    • Wenn nein, wird der default Wert zurückgegeben.
    • Der OpenFeature Client gibt nun den Wert zurück, welcher vom Provider zurückgegeben wurde.

Nun wollen wir unseren Code testen! Mittels node 05_openfeature_with_targeting.js starten wir unseren Server. Anschliessend nutzen wir curl, um einige Calls zu machen:

 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 |
                ||     ||% 

Und da ist sie unsere Kuh! 🐮

Unser OpenFeature Client ruft unseren InMemoryProvider bei jedem Request mit einem Kontext auf und evaluiert so das Feature Flag. Damit können wir steuern, dass nur ich (Tom) die Kuh sehe und alle anderen nicht.

An dieser Stelle ist es wichtig zu erwähnen, dass wir den InMemoryProvider nur für dieses Beispiel nutzen. Somit ist die FlagConfiguration im Code und nicht dezentral in einem anderen Service. Dadurch muss der Server auch bei jeder Änderung am Flag neu gestartet werden. In einem Real-World Szenario würden wir einen dezentralen Provider wie flagd nutzen, welcher die Konfiguration aus dem Code entfernt und so die Änderungen zur Laufzeit ermöglicht. Unser Server würde dann bei jedem Request einen Call via API an den Provider machen und dort werden die konfigurierten Feature Flags dann evaluiert. Dadurch erhalten wir eine klare Trennung zwischen Code und Konfiguration.

Fazit

Ich muss zugeben, anfangs war ich skeptisch, wieso braucht man Feature Flagging, wieso braucht man OpenFeature. Schnell wird aber klar, dass sich so Canary Releases, A/B Tests und vieles mehr einfach umsetzen lassen, ohne die komplette Infrastruktur und Architektur zu duplizieren. Dies spart Kosten und macht die Software flexibler, da Features deaktiviert deployed werden können und im Code “versteckt” sind. Die Vorteile kommen sicher noch besser zu tragen, wenn man Feature Flagging at scale betreibt und Features gleichzeitig über mehrere Services ausrollen bzw. steuern kann.

OpenFeatures sollte auf jeden Fall zu jedem Feature Flagging dazugehören, da es eine standardisierte und unabhängige Implementation bietet.

Hier können wir die letzte Frage beantworten: Ja, OpenFeature taugt was! 🚀

Ausblick

Wie geht es weiter? Ich könnte mir ein weiteres TechUp vorstellen, indem wir einen dezentralen Provider wie flagd anbinden und so die Konfiguration aus dem Code entfernen. Ausserdem wäre spannend, der Kubernetes OpenFeature Operator genau anschauen und Feature Flags als CRD zu definieren.

Mit diesen beiden Punkten nähern wir uns dann an ein enterprise ready Feature Flagging System an. 🚀

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.