Merkmalsflaggen mit Feder

Feature-Flags mit Frühling

1. Überblick

In diesem Artikel definieren wir kurz Feature-Flags und schlagen einen einfühlsamen und pragmatischen Ansatz vor, um sie in Spring Boot-Anwendungen zu implementieren. Anschließend werden wir uns mit komplexeren Iterationen befassen und dabei verschiedene Spring Boot-Funktionen nutzen.

Wir werden verschiedene Szenarien diskutieren, die möglicherweise das Markieren von Funktionen erfordern, und über mögliche Lösungen sprechen. Wir tun dies mit einer Bitcoin Miner-Beispielanwendung.

2. Feature-Flags

Feature-Flags - manchmal auch als Feature-Toggles bezeichnet - sind ein Mechanismus, mit dem wir bestimmte Funktionen unserer Anwendung aktivieren oder deaktivieren können, ohne den Code ändern oder im Idealfall unsere App erneut bereitstellen zu müssen.

Abhängig von der Dynamik, die für ein bestimmtes Feature-Flag erforderlich ist, müssen diese möglicherweise global, pro App-Instanz oder genauer konfiguriert werden - möglicherweise pro Benutzer oder Anforderung.

Wie in vielen Situationen des Software-Engineerings ist es wichtig, den einfachsten Ansatz zu verwenden, der das vorliegende Problem angeht, ohne unnötige Komplexität hinzuzufügen.

Feature-Flags sind ein wirksames Werkzeug, das bei kluger Verwendung Zuverlässigkeit und Stabilität in unser System bringen kann. Wenn sie jedoch missbraucht oder nicht ausreichend gewartet werden, können sie schnell zu Ursachen für Komplexität und Kopfschmerzen werden.

Es gibt viele Szenarien, in denen Feature-Flags nützlich sein könnten:

Stammbasierte Entwicklung und nicht triviale Funktionen

Bei der stammbasierten Entwicklung sind wir möglicherweise nicht bereit, eine bestimmte Funktionalität freizugeben, insbesondere wenn wir weiterhin häufig integrieren möchten. Feature-Flags können nützlich sein, damit wir sie weiterhin freigeben können, ohne dass unsere Änderungen bis zum Abschluss verfügbar sind.

Umgebungsspezifische Konfiguration

Möglicherweise benötigen wir bestimmte Funktionen, um unsere Datenbank für eine E2E-Testumgebung zurückzusetzen.

Alternativ müssen wir möglicherweise eine andere Sicherheitskonfiguration für Nicht-Produktionsumgebungen verwenden als in der Produktionsumgebung.

Daher könnten wir Feature-Flags nutzen, um das richtige Setup in der richtigen Umgebung umzuschalten.

A/B testing

Die Veröffentlichung mehrerer Lösungen für dasselbe Problem und die Messung der Auswirkungen ist eine überzeugende Technik, die wir mithilfe von Feature-Flags implementieren können.

Kanarische Freigabe

Wenn wir neue Funktionen bereitstellen, können wir uns dazu entschließen, dies schrittweise zu tun, beginnend mit einer kleinen Gruppe von Benutzern, und die Übernahme zu erweitern, während wir die Richtigkeit des Verhaltens überprüfen. Mit Feature-Flags können wir dies erreichen.

In den folgenden Abschnitten werden wir versuchen, einen praktischen Ansatz für die oben genannten Szenarien bereitzustellen.

Lassen Sie uns verschiedene Strategien für das Markieren von Funktionen aufschlüsseln, beginnend mit dem einfachsten Szenario, um dann zu einem detaillierteren und komplexeren Setup überzugehen.

3. Feature-Flags auf Anwendungsebene

Wenn wir einen der ersten beiden Anwendungsfälle angehen müssen, sind Feature-Flags auf Anwendungsebene eine einfache Möglichkeit, die Funktionsweise zu verbessern.

Ein einfaches Feature-Flag umfasst normalerweise eine Eigenschaft und eine Konfiguration, die auf dem Wert dieser Eigenschaft basiert.

3.1. Feature-Flags mit Federprofilen

Im Frühjahr können wirtake advantage of profiles. Conveniently, profiles enable us to configure certain beans selectively. With a few constructs around them, we can quickly create a simple and elegant solution for application-level feature flags.

Stellen wir uns vor, wir bauen ein BitCoin-Mining-System. Unsere Software befindet sich bereits in der Produktion und wir haben die Aufgabe, einen experimentellen, verbesserten Mining-Algorithmus zu erstellen.

In unserenJavaConfig konnten wir unsere Komponenten profilieren:

@Configuration
public class ProfiledMiningConfig {

    @Bean
    @Profile("!experimental-miner")
    public BitcoinMiner defaultMiner() {
        return new DefaultBitcoinMiner();
    }

    @Bean
    @Profile("experimental-miner")
    public BitcoinMiner experimentalMiner() {
        return new ExperimentalBitcoinMiner();
    }
}

Dannwith the previous configuration, we simply need to include our profile to opt-in for our new functionality. Es gibttons of ways of configuring our app im Allgemeinen undenabling profiles in particular. Ebenso gibt estesting utilities, um unser Leben leichter zu machen.

Solange unser System einfach genug ist,we could then create an environment-based configuration to determine which features flags to apply and which ones to ignore.

Stellen wir uns vor, wir haben eine neue Benutzeroberfläche, die auf Karten anstelle von Tabellen basiert, zusammen mit dem vorherigen experimentellen Miner.

Wir möchten beide Funktionen in unserer Akzeptanzumgebung (UAT) aktivieren. Wir könnten eineapplication-uat.yml-Datei erstellen:

spring:
  profiles:
    include: experimental-miner,ui-cards

# More config here

Mit der vorherigen Datei müssen wir nur das UAT-Profil in der UAT-Umgebung aktivieren, um die gewünschten Funktionen zu erhalten.

Es ist auch wichtig zu verstehen, wiespring.profiles.include. genutzt werden können. Im Vergleich zuspring.profiles.active, können wir mit ersteren Profile additiv einbeziehen.

In unserem Fall möchten wir, dass dasuat-Profil auch Experimental-Miner- und UI-Karten. enthält

3.2. Feature-Flags mit benutzerdefinierten Eigenschaften

Profile sind eine großartige und einfache Möglichkeit, die Arbeit zu erledigen. Für andere Zwecke benötigen wir jedoch möglicherweise Profile. Vielleicht möchten wir auch eine strukturiertere Infrastruktur für Feature-Flags aufbauen.

In diesen Szenarien können benutzerdefinierte Eigenschaften eine wünschenswerte Option sein.

Let’s rewrite our previous example taking advantage of @ConditionalOnProperty and our namespace:

@Configuration
public class CustomPropsMiningConfig {

    @Bean
    @ConditionalOnProperty(
      name = "features.miner.experimental",
      matchIfMissing = true)
    public BitcoinMiner defaultMiner() {
        return new DefaultBitcoinMiner();
    }

    @Bean
    @ConditionalOnProperty(
      name = "features.miner.experimental")
    public BitcoinMiner experimentalMiner() {
        return new ExperimentalBitcoinMiner();
    }
}

Das vorherige Beispiel baut auf der bedingten Konfiguration von Spring Boot auf und konfiguriert die eine oder andere Komponente, je nachdem, ob die Eigenschaft auftrue oderfalse festgelegt ist (oder ganz weggelassen wird).

Das Ergebnis ist dem in 3.1 sehr ähnlich, aber jetzt haben wir unseren Namespace. Mit unserem Namespace können wir aussagekräftige YAML- / Eigenschaftendateien erstellen:

#[...] Some Spring config

features:
  miner:
    experimental: true
  ui:
    cards: true

#[...] Other feature flags

Mit diesem neuen Setup können wir unseren Feature-Flags auch ein Präfix voranstellen - in unserem Fall mit dem Präfix.features

Es mag wie ein kleines Detail erscheinen, aber wenn unsere Anwendung wächst und die Komplexität zunimmt, hilft uns diese einfache Iteration, unsere Feature-Flags unter Kontrolle zu halten.

Lassen Sie uns über andere Vorteile dieses Ansatzes sprechen.

3.3. Verwenden von @ConfigurationProperties

Sobald wir einen vorangestellten Satz von Eigenschaften erhalten, können wir einPOJO decorated with @ConfigurationProperties erstellen, um ein programmatisches Handle in unserem Code zu erhalten.

Folgen Sie unserem Beispiel:

@Component
@ConfigurationProperties(prefix = "features")
public class ConfigProperties {

    private MinerProperties miner;
    private UIProperties ui;

    // standard getters and setters

    public static class MinerProperties {
        private boolean experimental;
        // standard getters and setters
    }

    public static class UIProperties {
        private boolean cards;
        // standard getters and setters
    }
}

Indem wir den Status unserer Feature-Flags in eine zusammenhängende Einheit setzen, eröffnen wir neue Möglichkeiten, die es uns ermöglichen, diese Informationen problemlos anderen Teilen unseres Systems wie der Benutzeroberfläche oder nachgeschalteten Systemen zugänglich zu machen.

3.4. Funktionskonfiguration verfügbar machen

Unser Bitcoin-Mining-System hat ein UI-Upgrade erhalten, das noch nicht vollständig fertig ist. Aus diesem Grund haben wir uns entschlossen, das Feature zu kennzeichnen. Möglicherweise haben wir eine einseitige App, die React, Angular oder Vue verwendet.

Unabhängig von der Technologiewe need to know what features are enabled so that we can render our page accordingly.

Erstellen wir einen einfachen Endpunkt für unsere Konfiguration, damit unsere Benutzeroberfläche das Backend bei Bedarf abfragen kann:

@RestController
public class FeaturesConfigController {

    private ConfigProperties properties;

    // constructor

    @GetMapping("/feature-flags")
    public ConfigProperties getProperties() {
        return properties;
    }
}

Es gibt möglicherweise ausgefeiltere Möglichkeiten, diese Informationen bereitzustellen, z. B.creating custom actuator endpoints. Im Sinne dieses Handbuchs scheint ein Controller-Endpunkt eine gute Lösung zu sein.

3.5. Das Lager sauber halten

Obwohl es offensichtlich klingen mag, ist es nach sorgfältiger Implementierung unserer Feature-Flags ebenso wichtig, diszipliniert zu bleiben, wenn sie nicht mehr benötigt werden.

Feature flags for the first use case – trunk-based development and non-trivial features – are typically short-lived. Dies bedeutet, dass wir sicherstellen müssen, dass unsereConfigProperties, unsere Java-Konfiguration und unsereYAML Dateien sauber und aktuell bleiben.

4. Weitere granulare Feature-Flags

Manchmal befinden wir uns in komplexeren Szenarien. Für A / B-Tests oder kanarische Freisetzungen reicht unser bisheriger Ansatz einfach nicht aus.

Um Feature-Flags auf einer detaillierteren Ebene zu erhalten, müssen wir möglicherweise unsere Lösung erstellen. Dies kann das Anpassen unserer Benutzerentität mit funktionsspezifischen Informationen oder das Erweitern unseres Webframeworks umfassen.

Die Verschmutzung unserer Benutzer mit Feature-Flags ist jedoch möglicherweise nicht für alle eine ansprechende Idee, und es gibt auch andere Lösungen.

Alternativ könnten wir einige integrierte Toolssuch as Togglz nutzen. Dieses Tool erhöht die Komplexität, bietet jedoch eine sofort einsatzbereite Lösung undprovides first-class integration with Spring Boot.

Togglz unterstützt verschiedeneactivation strategies:

  1. Username: Flags, die bestimmten Benutzern zugeordnet sind

  2. Gradual rollout: Flags für einen Prozentsatz der Benutzerbasis aktiviert. Dies ist nützlich für kanarische Versionen, wenn wir beispielsweise das Verhalten unserer Funktionen überprüfen möchten

  3. Release date: Wir können festlegen, dass Flags zu einem bestimmten Datum und einer bestimmten Uhrzeit aktiviert werden. Dies kann für eine Produkteinführung, eine koordinierte Veröffentlichung oder für Angebote und Rabatte nützlich sein

  4. Client IP: Markierte Funktionen basierend auf Client-IPs. Dies kann nützlich sein, wenn die spezifische Konfiguration auf bestimmte Kunden angewendet wird, da diese statische IP-Adressen haben

  5. Server IP: In diesem Fall wird die IP des Servers verwendet, um zu bestimmen, ob eine Funktion aktiviert werden soll oder nicht. Dies kann auch für kanarische Veröffentlichungen nützlich sein, mit einem etwas anderen Ansatz als der schrittweisen Einführung - wie zum Beispiel, wenn wir die Auswirkungen auf die Leistung in unseren Instanzen bewerten möchten

  6. ScriptEngine: Wir könnten Feature-Flags basierend aufarbitrary scripts aktivieren. Dies ist wohl die flexibelste Option

  7. System Properties: Wir können bestimmte Systemeigenschaften festlegen, um den Status eines Feature-Flags zu bestimmen. Dies wäre ziemlich ähnlich zu dem, was wir mit unserem einfachsten Ansatz erreicht haben

5. Zusammenfassung

In diesem Artikel hatten wir die Gelegenheit, über Feature-Flags zu sprechen. Darüber hinaus haben wir besprochen, wie Spring uns dabei helfen kann, einige dieser Funktionen zu erreichen, ohne neue Bibliotheken hinzuzufügen.

Wir haben zunächst definiert, wie dieses Muster uns bei einigen häufigen Anwendungsfällen helfen kann.

Als Nächstes haben wir einige einfache Lösungen unter Verwendung der Standardwerkzeuge Spring und Spring Boot erstellt. Damit haben wir ein einfaches, aber leistungsstarkes Feature-Flagging-Konstrukt entwickelt.

Unten haben wir einige Alternativen verglichen. Übergang von der einfacheren und weniger flexiblen Lösung zu einem komplexeren Muster.

Abschließend haben wir kurz einige Richtlinien zur Erstellung robusterer Lösungen angegeben. Dies ist nützlich, wenn wir einen höheren Grad an Granularität benötigen.