15. Jun 2017
Lesedauer 11 Min.
Fun with Flags
Feature-Flags richtig implementieren
Erhalten Sie die volle Kontrolle über die Auslieferung Ihrer Lösung.

In Zeiten von DevOps zählen Feature-Flags wohl zu den wichtigsten Patterns überhaupt – und doch gibt es noch viele Teams, die sich noch nicht damit beschäftigt haben. Zu den DevOps-Praktiken gehört es auch, unterbrechungsfrei zu installieren (Zero-Downtime-Deployment), Deployments mit einem kleineren Benutzerkreis zu testen (Canary-Releases), Experimente mit unterschiedlichen Versionen von Software durchzuführen (A/B-Testing) und Funktionalität optional anzubieten (Opt-In). Das alles wird nur durch den Einsatz von Feature-Flags möglich.Feature-Flags sind ein Design-Pattern, bei dem bestimmte Funktionen (Features) abhängig von einer Konfiguration zur Laufzeit aktiviert oder deaktiviert werden können [1]. Ursprünglich wurden Feature-Flags wegen ihrer booleschen Natur als Feature-Toggle [2] bezeichnet. Ein anderer bekannter Name für dieses Pattern ist Feature-Switch. Mit der zunehmenden Bedeutung von A/B-Testing und Canary-Releases wurden die Toggles komplexer und es hat sich der Name Feature-Flags emanzipiert [3].Die Konfiguration von Feature-Flags kann von folgenden Bedingungen abhängen:
- dem Benutzer oder einem Benutzerattribut (Land, Name, Gruppenmitgliedschaft et cetera),
- der Umgebung (Server, Netzwerk, Mandant et cetera),
- dem Zufall.

Einsatzvon Feature-Flags(Bild 1)
Autor
Eine Mischung dieser Bedingungen ist ebenfalls erlaubt. Zum Beispiel können Sie ein Feature für zwanzig Prozent aller Benutzer in einem bestimmten Land aktivieren (Bild 1). Je nach Einsatzgebiet unterscheidet man verschiedene Kategorien von Feature-Flags:
- Release-Flags,
- Deployment-Flags,
- Experiment-Flags,
- Operations-Flags (auch Ops-Flags genannt),
- Permission-Flags.
- Verwaltung der Feature-Flags an einer zentralen Stelle außerhalb der Anwendung.
- Änderung der Konfiguration zur Laufzeit ohne Downtime.
- Gleichzeitiges Ändern der Konfiguration in allen Komponenten und auch auf allen Systemen.
- Ändern der Konfiguration auf Basis von Benutzer, Maschine, prozentualer Anteile et cetera.
- Optimale Performance und Ausfallsicherheit.

Ein Feature-Flagerstellen(Bild 2)
Autor
Dies kann sinnvoll sein, wenn man zum Beispiel Permission-Flags für unterschiedliche Preismodelle verwenden möchte. So kann man in einem einzigen Flag zwischen Basic, Premium VIP et cetera unterscheiden und das Design und die Features entsprechend anpassen. Wird ein Flag nicht als permanent markiert, so bekommt man von LaunchDarkly eine Aufforderung, das Flag zu löschen, wenn es an alle Benutzer ausgerollt ist.Anschließend kann man dem Projekt in Visual Studio das NuGet-Paket LaunchDarkly.Client hinzufügen. Als Erstes muss man nun ein LdClient-Objekt erstellen. Diesem gibt man im Konstruktor den SDK-Key der gewünschten Umgebung (Dev/Test/Prod) mit. Dieser sollte natürlich aus der Konfiguration gelesen werden. Die SDK-Keys erhält man aus der Konfiguration der Umgebungen unter Account settings – siehe Bild 3. Dort kann man auch neue Umgebungen anlegen oder bestehende umbenennen.

Umgebungenverwalten(Bild 3)
Autor
Als Nächstes erstellt man ein Objekt vom Typ LaunchDarkly.Client.User. Das Objekt hat ein Fluent-Interface zum Setzen der Attribute. Es stehen unter anderem Key, Name, Vorname, Land, IPAdresse zur Verfügung – es können aber auch weitere Attribute hinzugefügt werden.Die Benutzer werden in LaunchDarkly automatisch angelegt und aktualisiert. Zum Prüfen von normalen Flags gibt es die Methode BoolVariation. Sie übernimmt das User-Objekt, den Namen des Flags und einen Standardwert als Parameter und gibt True oder False zurück.Für multivariante Flags gibt es die Methoden IntVariation, FloatVariation, StringVariation und JsonVariation mit denselben Parametern. Letztere liefert ein Objekt vom Typ Newtonsoft.Json.Linq.JToken.
Listing 1: Feature-Flag im Code
using (<span class="hljs-keyword">var</span> client = new LdClient(<br/> ConfigurationManager.AppSettings[<span class="hljs-string">"EnvironmentKey"</span>])) <br/>{ <br/> User user = LaunchDarkly.Client.User <br/> .WithKey(<span class="hljs-keyword">this</span>.CurrentUser?.Id ?? <br/> Session.SessionID) <br/> .AndIpAddress(<br/> <span class="hljs-keyword">this</span>.HttpContext.Request.UserHostAddress) <br/> .AndAnonymous(<span class="hljs-keyword">this</span>.CurrentUser == <span class="hljs-literal">null</span>); <br/> <br/> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.CurrentUser != <span class="hljs-literal">null</span>) <br/> { <br/> user = user.AndEmail(<span class="hljs-keyword">this</span>.CurrentUser.Email) <br/> .AndFirstName(<span class="hljs-keyword">this</span>.CurrentUser.FirstName) <br/> .AndLastName(<span class="hljs-keyword">this</span>.CurrentUser.LastName) <br/> .AndCountry(<span class="hljs-keyword">this</span>.CurrentUser.Country) <br/> .AndCustomAttribute(<span class="hljs-string">"City"</span>, <span class="hljs-keyword">this</span>.CurrentUser.City) <br/> .AndCustomAttribute(<span class="hljs-string">"Roles"</span>, <br/> <span class="hljs-keyword">this</span>.CurrentUser.Roles?.Select(<br/> x =&gt; x.RoleId).ToList()); <br/> } <br/> <br/> ViewBag.AccountType = client.BoolVariation(<br/> <span class="hljs-string">"perm-premium-account"</span>, user, <span class="hljs-literal">false</span>) <br/> ? <span class="hljs-string">"&lt;&lt;Premium Account&gt;&gt;"</span> <br/> : <span class="hljs-string">"&lt;&lt;Normal Account&gt;&gt;"</span>; <br/> <br/> ViewBag.AccountColor = client.StringVariation(<br/> <span class="hljs-string">"perm-account-color"</span>, user, <span class="hljs-string">"green"</span>); <br/>}
Den Code eines kompletten Beispiels aus einer ASP-Anwendung finden Sie in Listing 1.Bis auf ein paar Ausnahmen, die permanent in einer Anwendung bestehen, sollten Flags wieder entfernt werden, sobald das Feature an alle Benutzer ausgerollt ist. Wird dies nicht gemacht, dann entsteht eine technische Schuld – eine sogenannte technical debt [13]. Um diese zu kontrollieren, gilt es von vornherein ein System zu entwickeln, um Flags über ihren gesamten Lebenszyklus zu verwalten.Wie man ein Flag wieder entfernen kann, weiß man am besten zu dem Zeitpunkt, zu dem es erstellt wird. Wird nun ein Feature in einem Feature-Branch (zum Beispiel /features/awesome-xyz-support) entwickelt, dann wird dort ein Flag eingeführt.
<span class="hljs-params">...</span>
<span class="hljs-keyword">if</span> (client.BoolVariation(
<span class="hljs-string">"temp-awsome-xyz"</span>, user, <span class="hljs-literal">false</span>))
do_new_thing();
<span class="hljs-keyword">else</span>
do_old_thing();
<span class="hljs-params">...</span>
Bevor jetzt ein Pull-Request erstellt wird, wird ein zweiter Branch (zum Beispiel /cleanup/awsome-xyz-support) angelegt. In diesem Branch wird das Flag wieder entfernt.
...
do_new_thing();
...
Hier ist es wichtig, gleich ein richtiges System an Konventionen zu implementieren.Das Feature-Flag wird in diesem Beispiel mit dem Präfix temp versehen. Dadurch ist sofort erkenntlich, dass das Flag nicht permanent im Code verbleiben soll. Der Branch unter cleanup erhält denselben Namen wie das Feature-Flag. Somit ist sofort ersichtlich, dass das Flag über diesen wieder entfernt werden kann, wenn es nicht mehr benötigt wird. Die Namenskonventionen sind nur Bespiele und können für jede Umgebung angepasst werden.Wird nun der erste Pull-Request erfolgreich abgeschlossen und das neue Feature zusammen mit dem Flag zusammengeführt, kann sofort ein neuer Pull-Request für den cleanup-Branch erstellt werden. Das Flag lässt sich dann jederzeit wieder durch Abschließen des Pull-Requests entfernen, solange es keine Merge-Konflikte gibt [14].Außerdem sollten Flags regelmäßig kontrolliert werden. Es empfiehlt sich, eine Kennzahl für temporäre Flags zusammen mit den anderen Kennzahlen für die technische Schuld zu überwachen.Auf diese Weise wird sichergestellt, dass die Anzahl an temporären Flags nicht kontinuierlich steigt. Ein regelmäßiger Review kann auch in Sprint-Meetings integriert werden. So ist gesichert, dass alle Teammitglieder über die aktuell ausgerollten Feature-Flags Bescheid wissen.Um die Feature-Flags zu aktivieren, gibt es mehrere Optionen (Bild 4). Generell hat ein Feature einen Kill-Switch, mit dem es komplett deaktiviert werden kann. Dieser ist sowohl in der Feature-Liste (1) als auch in der Verwaltungsseite (2) verfügbar und wird gerne am Anfang übersehen. Über Prerequisites (3) können einem Flag andere Flags als Vorbedingung zugeordnet werden. So lassen sich neue Datenbanken oder API-Versionen unabhängig von neuen UI-Features entwickeln und neue Features trotzdem nur dann aktivieren, wenn die Vorbedingungen erfüllt sind.

Benutzerzuordnen(Bild 4)
Autor
Benutzer können dann entweder direkt (4) oder über Regeln (5) adressiert werden. Bei den Regeln kann das Flag entweder direkt angegeben werden, oder es wird ein prozentualer Anteil verwendet. So könnte ein Flag zum Beispiel für 50 Prozent aller Benutzer in Deutschland freigeschaltet werden. Es lassen sich beliebig viele Regeln angeben, die der Reihe nach abgearbeitet werden. Trifft keine Regel zu, dann gilt die Default-Regel (6). Diese kann Verwendung finden, wenn ein rein prozentualer Anteil ohne weitere Einschränkungen freigeschaltet werden soll.Um Experimente (A/B-Testing) durchführen zu können, unterstützt LaunchDarkly durch das Sammeln von Seitenaufrufen, Klicks und benutzerdefinierten Ereignissen. Für die ersten beiden muss das JavaScript-SDK installiert werden. Dieses kann entweder über den Node Paket Manager installiert (npm install --save ldclient-js) oder direkt aus dem CDN von LaunchDarkly geladen werden (siehe Listing 2).
Listing 2: Installation des JavaScript-SDK
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://app.launchdarkly.com/snippet/</span></span><br/><span class="hljs-tag"><span class="hljs-string"> ldclient.min.js"</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> <br/><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>&gt;</span><span class="actionscript"> </span><br/><span class="actionscript"> <span class="hljs-keyword">var</span> user = { </span><br/><span class="actionscript"> <span class="hljs-string">"key"</span>: <span class="hljs-string">"@ViewBag.UserKey"</span> </span><br/><span class="actionscript"> }; </span><br/><span class="actionscript"> <span class="hljs-keyword">var</span> ldclient = LDClient.initialize(</span><br/><span class="actionscript"> <span class="hljs-string">'@ViewBag.EnvironmentKey'</span>, user); </span><br/><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
Der LDClient ist mit dem Key für die Umgebung und einem Benutzer zu initialisieren. Wichtig: Es muss die Client-side ID und nicht der SDK-Key verwendet werden (siehe Bild 5). Um auch Klick-Events auswerten zu können, müssen Sie die Anzeige in einem IFrame erlauben. Dies klappt über die Angabe zusätzlicher HTTP-Header, wie in Listing 3. Ist dies alles geschehen, wird die eigene Seite – wie in Bild 5 – durch Eingabe des URL (1) in einem IFrame angezeigt. Mithilfe der Developer Tools des Browsers Chrome können Sie nun den CSS-Selector für das gewünschte Klickziel kopieren (2) und in das Feld CSS selector eingeben (3). Der Selector wird validiert und das Ergebnis grafisch neben dem Feld dargestellt.

Zielefestlegen(Bild 5)
Autor
Für benutzerdefinierte Ereignisse bietet der Client aus dem SDK die Methode Track an. Ihr übergeben Sie den Benutzer, den Namen des Ereignisses und optional einen Text mit zusätzlichen Daten. Mehr ist nicht notwendig.Um das Experiment durchzuführen, werden einem Feature-Flag über dessen Einstellungen nun ein oder mehrere Ziele zugeordnet. An dieser Stelle werden später auch die Ergebnisse dargestellt. Sie sollten allerdings nicht gleich nervös werden, wenn die ersten Ergebnisse auf sich warten lassen, denn diese werden immer mit einem Zeitversatz von fünf Minuten berechnet. Der Gewinner eines Experiments wird erst dann ausgewiesen, wenn mehr als 1.000 Benutzer teilgenommen haben und das Vertrauen in das Ergebnis (Confidence) über 95 Prozent liegt.
Listing 3: HTTP-Header für Klick-Events
<span class="hljs-tag">&lt;<span class="hljs-name">system.webServer</span>&gt;</span> <br/> <span class="hljs-tag">&lt;<span class="hljs-name">httpProtocol</span>&gt;</span> <br/> <span class="hljs-tag">&lt;<span class="hljs-name">customHeaders</span>&gt;</span> <br/> <span class="hljs-tag">&lt;<span class="hljs-name">add</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"X-Frame-Options"</span> <span class="hljs-attr">value</span>=</span><br/><span class="hljs-tag"> <span class="hljs-string">"ALLOW-FROM https://app.launchdarkly.com"</span>/&gt;</span> <br/> <span class="hljs-tag">&lt;<span class="hljs-name">add</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Content-Security-Policy"</span> <span class="hljs-attr">value</span>=</span><br/><span class="hljs-tag"> <span class="hljs-string">"frame-ancestors 'self' </span></span><br/><span class="hljs-tag"><span class="hljs-string"> https://app.launchdarkly.com"</span>/&gt;</span> <br/> <span class="hljs-tag">&lt;/<span class="hljs-name">customHeaders</span>&gt;</span> <br/> <span class="hljs-tag">&lt;/<span class="hljs-name">httpProtocol</span>&gt;</span> <br/><span class="hljs-tag">&lt;/<span class="hljs-name">system.webServer</span>&gt;</span>
LaunchDarkly lässt sich über eine Extension [15] in Visual Studio Team Services (VSTS) und Team Foundation Server (TFS) integrieren. Die Extension erlaubt es, einem WorkItem eines oder mehrere Feature-Flags zuzuordnen. Über einen Release-Task lässt sich dann ein prozentualer Rollout in die Release-Pipeline integrieren. Dies erlaubt es, vollautomatisierte prozentuale Rollouts (Canary-Releases) durchzuführen und den Prozentsatz schrittweise zu erhöhen, sofern vorgegebene Grenzwerte erreicht werden. Das Ergebnis des Rollouts steht dann im WorkItem-Formular.Arbeiten Sie nicht mit TFS/VSTS, können Sie LaunchDarkly auch in eine Bitbucket-Pipeline integrieren. Hilft das auch nicht weiter, können Sie sich mithilfe von Webhooks eine eigene Integration „basteln". So sollte eine Integration in jede DevOps-Lösung gewährleistet sein.Wer sich bisher noch nicht mit Feature-Flags beschäftigt hat, der sollte dringend damit beginnen. In Zeiten von DevOps kommt man an diesem Pattern kaum vorbei, denn es bietet enorme Möglichkeiten. Aber man sollte den Aufwand nicht unterschätzen. Ein eigenes System für die Verwaltung bedeutet einen hohen Aufwand – und wenn Sie noch keine Erfahrung mit dem Thema haben, ist die Chance groß, dass das Ganze im Chaos endet. Aus diesem Grund habe ich hier LaunchDarkly ausführlich vorgestellt. Als Feature-Flag as a Service bietet es einen leichten Einstieg in das Thema. Sie können sich voll auf die richtige Verwendung des vorgegebenen Systems konzentrieren und vermeiden damit Chaos und die Gefahr einer großen technischen Schuld.Richtig eingesetzt sind Feature-Flags eines der mächtigsten Patterns für DevOps. Man sollte das Thema deshalb ernst nehmen und richtig angehen.
Fussnoten
- Martin Fowler – Feature Toggles, http://www.dotnetpro.de/SL1707FeatureFlags1
- Wikipedia – Feature Toggle, http://www.dotnetpro.de/SL1707FeatureFlags2
- LaunchDarkly – Is it a feature flag or a feature toggle?, http://www.dotnetpro.de/SL1707FeatureFlags3
- FeatureSwitcher, http://www.dotnetpro.de/SL1707FeatureFlags4
- NFeature, http://www.dotnetpro.de/SL1707FeatureFlags5
- FlipIt, http://www.dotnetpro.de/SL1707FeatureFlags6
- FeatureToggle, http://www.dotnetpro.de/SL1707FeatureFlags7
- FeatureBee, http://www.dotnetpro.de/SL1707FeatureFlags8
- writeabout.net – There is no DevOps without feature flags!, http://www.dotnetpro.de/SL1707FeatureFlags9
- Homepage von LaunchDarkly, http://www.dotnetpro.de/SL1707FeatureFlags10
- writeabout.net – Roll out a service fabric application with LaunchDarkly and VSTS, http://www.dotnetpro.de/SL1707FeatureFlags11
- LaunchDarkly testen, http://www.dotnetpro.de/SL1707FeatureFlags12
- Technische Schuld (technical debt), http://www.dotnetpro.de/SL1707FeatureFlags13
- LaunchDarkly – How to use feature flags without technical debt, http://www.dotnetpro.de/SL1707FeatureFlags14
- LaunchDarkly Extension, http://www.dotnetpro.de/SL1707FeatureFlags15