Drapeaux avec le printemps

Drapeaux avec le printemps

1. Vue d'ensemble

Dans cet article, nous allons définir brièvement les indicateurs de fonctionnalités et proposer une approche avisée et pragmatique pour les implémenter dans les applications Spring Boot. Ensuite, nous explorerons des itérations plus sophistiquées en tirant parti des différentes fonctionnalités de Spring Boot.

Nous aborderons divers scénarios pouvant nécessiter un marquage des fonctionnalités et discuterons des solutions possibles. Nous allons le faire en utilisant un exemple d'application Bitcoin Miner.

2. Indicateurs de fonctionnalité

Les indicateurs de fonctionnalité - parfois appelés bascule de fonctionnalité - sont un mécanisme qui nous permet d'activer ou de désactiver des fonctionnalités spécifiques de notre application sans avoir à modifier le code ou, idéalement, à redéployer notre application.

En fonction de la dynamique requise par un indicateur de fonctionnalité donné, il peut être nécessaire de les configurer globalement, par instance d'application ou de manière plus granulaire, par utilisateur ou par demande.

Comme dans de nombreuses situations en génie logiciel, il est important d'essayer d'utiliser l'approche la plus simple qui s'attaque au problème en question sans ajouter de complexité inutile.

Les indicateurs de fonctionnalités sont un outil puissant qui, lorsqu'il est utilisé à bon escient, peut apporter fiabilité et stabilité à notre système. Cependant, lorsqu'ils sont mal utilisés ou sous-entretenus, ils peuvent rapidement devenir source de complexité et de maux de tête.

Il existe de nombreux scénarios dans lesquels les indicateurs de fonctionnalité pourraient être utiles:

Développement basé sur le tronc et fonctionnalités non triviales

Dans le développement basé sur le coffre, en particulier lorsque nous souhaitons continuer à nous intégrer fréquemment, nous risquons de ne pas être prêts à proposer une fonctionnalité donnée. Les drapeaux de fonctionnalité peuvent être utiles pour nous permettre de continuer à publier sans rendre nos modifications disponibles jusqu’à leur achèvement.

Configuration spécifique à l'environnement

Nous pourrions avoir besoin de certaines fonctionnalités pour réinitialiser notre base de données dans un environnement de test E2E.

Alternativement, nous pourrions avoir besoin d'utiliser une configuration de sécurité différente pour les environnements de non-production que celle utilisée dans l'environnement de production.

Par conséquent, nous pourrions tirer parti des indicateurs de fonctionnalité pour basculer la bonne configuration dans le bon environnement.

A/B testing

La publication de plusieurs solutions pour le même problème et la mesure de l’impact sont une technique convaincante que nous pourrions implémenter à l’aide d’indicateurs de caractéristiques.

Canari libérant

Lors du déploiement de nouvelles fonctionnalités, nous pouvons décider de le faire progressivement, en commençant par un petit groupe d'utilisateurs et en développant son adoption au fur et à mesure que nous validons l'exactitude de son comportement. Les drapeaux de fonctionnalité nous permettent d'y parvenir.

Dans les sections suivantes, nous tenterons de proposer une approche pratique pour aborder les scénarios mentionnés ci-dessus.

Décomposons différentes stratégies pour le marquage des fonctionnalités, en commençant par le scénario le plus simple pour ensuite passer à une configuration plus granulaire et plus complexe.

3. Indicateurs de fonctionnalité au niveau de l'application

Si nous devons aborder l'un des deux premiers cas d'utilisation, les indicateurs de fonctionnalités au niveau de l'application constituent un moyen simple de faire fonctionner les choses.

Un simple indicateur de fonctionnalité implique généralement une propriété et une configuration basée sur la valeur de cette propriété.

3.1. Drapeaux de fonctionnalité utilisant des profils de ressort

Au printemps, nous pouvonstake 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.

Imaginons que nous construisions un système de minage BitCoin. Notre logiciel est déjà en production et nous sommes chargés de créer un algorithme d'exploration expérimental amélioré.

Dans nosJavaConfig, nous pourrions profiler nos composants:

@Configuration
public class ProfiledMiningConfig {

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

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

Ensuite,with the previous configuration, we simply need to include our profile to opt-in for our new functionality. Il y atons of ways of configuring our app en général etenabling profiles in particular. De même, il existe destesting utilities pour nous faciliter la vie.

Tant que notre système est assez simple,we could then create an environment-based configuration to determine which features flags to apply and which ones to ignore.

Imaginons que nous ayons une nouvelle interface utilisateur basée sur des cartes au lieu de tables, avec le mineur expérimental précédent.

Nous souhaitons activer les deux fonctionnalités dans notre environnement d'acceptation (UAT). Nous pourrions créer un fichierapplication-uat.yml:

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

# More config here

Une fois le fichier précédent en place, nous devrons simplement activer le profil UAT dans l'environnement UAT pour obtenir l'ensemble de fonctionnalités souhaité.

Il est également important de comprendre comment tirer parti despring.profiles.include. Par rapport àspring.profiles.active,, le premier nous permet d'inclure des profils de manière additive.

Dans notre cas, nous voulons que le profiluat inclue également expérimental-miner et ui-cards.

3.2. Indicateurs de fonctionnalité à l'aide de propriétés personnalisées

Les profils sont un moyen simple et efficace de faire le travail. Cependant, nous pourrions avoir besoin de profils à d'autres fins. Ou peut-être pourrions-nous créer une infrastructure d'indicateurs de caractéristiques plus structurée.

Pour ces scénarios, les propriétés personnalisées peuvent être une option souhaitable.

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

L'exemple précédent s'appuie sur la configuration conditionnelle de Spring Boot et configure un composant ou un autre, selon que la propriété est définie surtrue oufalse (ou totalement omise).

Le résultat est très similaire à celui de la version 3.1, mais nous avons maintenant notre espace de noms. Avoir notre espace de noms nous permet de créer des fichiers de propriétés / YAML significatifs:

#[...] Some Spring config

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

#[...] Other feature flags

De plus, cette nouvelle configuration nous permet de préfixer nos indicateurs de fonctionnalités - dans notre cas, en utilisant le préfixefeatures.

Cela peut sembler un petit détail, mais à mesure que notre application grandit et que sa complexité augmente, cette simple itération nous aidera à garder le contrôle de nos indicateurs de fonctionnalités.

Parlons des autres avantages de cette approche.

3.3. Utilisation de @ConfigurationProperties

Dès que nous obtenons un ensemble préfixé de propriétés, nous pouvons créer unPOJO decorated with @ConfigurationProperties pour obtenir un handle de programmation dans notre code.

En suivant notre exemple actuel:

@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
    }
}

En plaçant l'état de nos indicateurs de fonctionnalités dans une unité cohérente, nous ouvrons de nouvelles possibilités, nous permettant d'exposer facilement ces informations à d'autres parties de notre système, telles que l'interface utilisateur, ou aux systèmes en aval.

3.4. Présentation de la configuration des fonctionnalités

Notre système d’exploitation de Bitcoin a obtenu une mise à niveau de l’UI qui n’est pas encore tout à fait prête. Pour cette raison, nous avons décidé de le signaler. Nous pourrions avoir une application d'une seule page utilisant React, Angular ou Vue.

Quelle que soit la technologie,we need to know what features are enabled so that we can render our page accordingly.

Créons un point de terminaison simple pour servir notre configuration afin que notre interface utilisateur puisse interroger le backend en cas de besoin:

@RestController
public class FeaturesConfigController {

    private ConfigProperties properties;

    // constructor

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

Il peut y avoir des moyens plus sophistiqués de servir ces informations, commecreating custom actuator endpoints. Mais pour les besoins de ce guide, un point de terminaison de contrôleur est une solution suffisamment satisfaisante.

3.5. Garder le camp propre

Bien que cela puisse paraître évident, une fois que nous avons mis en œuvre nos indicateurs de fonctionnalités de manière réfléchie, il est tout aussi important de rester discipliné pour les éliminer une fois qu'ils ne sont plus nécessaires.

Feature flags for the first use case – trunk-based development and non-trivial features – are typically short-lived. Cela signifie que nous allons devoir nous assurer que notreConfigProperties, notre configuration Java et nos fichiersYAML restent propres et à jour.

4. Drapeaux de caractéristiques plus granulaires

Parfois, nous nous trouvons dans des scénarios plus complexes. Pour les tests A / B ou les versions Canary, notre approche précédente n'est tout simplement pas suffisante.

Pour obtenir des indicateurs de fonctionnalité à un niveau plus granulaire, il peut être nécessaire de créer notre solution. Cela pourrait impliquer la personnalisation de notre entité utilisateur pour inclure des informations spécifiques à une fonctionnalité, ou peut-être l'extension de notre infrastructure Web.

Polluer nos utilisateurs avec des drapeaux de fonctionnalités pourrait ne pas être une idée attrayante pour tout le monde, cependant, et il existe d'autres solutions.

Comme alternative, nous pourrions profiter de certains outils intégréssuch as Togglz. Cet outil ajoute une certaine complexité mais offre une belle solution prête à l'emploi et desprovides first-class integration with Spring Boot.

Togglz prend en charge différentsactivation strategies:

  1. Username: Indicateurs associés à des utilisateurs spécifiques

  2. Gradual rollout: Indicateurs activés pour un pourcentage de la base d'utilisateurs. Ceci est utile pour les versions de Canary, par exemple, lorsque nous voulons valider le comportement de nos fonctionnalités.

  3. Release date: Nous pourrions programmer l'activation des indicateurs à une certaine date et heure. Cela peut être utile pour un lancement de produit, une version coordonnée, ou des offres et des remises.

  4. Client IP: Fonctionnalités marquées en fonction des adresses IP des clients. Celles-ci peuvent s'avérer utiles lors de l'application de la configuration spécifique à des clients spécifiques, car ils ont des adresses IP statiques.

  5. Server IP: Dans ce cas, l'adresse IP du serveur est utilisée pour déterminer si une fonction doit être activée ou non. Cela pourrait également être utile pour les publications de Canary, avec une approche légèrement différente de celle du déploiement progressif, comme lorsque nous voulons évaluer l'impact sur les performances dans nos instances

  6. ScriptEngine: Nous pourrions activer les indicateurs de fonctionnalité basés surarbitrary scripts. C'est sans doute l'option la plus flexible

  7. System Properties: Nous pourrions définir certaines propriétés système pour déterminer l'état d'un indicateur de fonction. Ce serait assez similaire à ce que nous avons réalisé avec notre approche la plus simple.

5. Sommaire

Dans cet article, nous avons eu l'occasion de parler des indicateurs de fonctionnalités. De plus, nous avons discuté de la manière dont Spring pourrait nous aider à atteindre certaines de ces fonctionnalités sans ajouter de nouvelles bibliothèques.

Nous avons commencé par définir comment ce modèle pouvait nous aider avec quelques cas d'utilisation courants.

Ensuite, nous avons élaboré quelques solutions simples à l’aide d’outils prêts à l’emploi, Spring et Spring Boot. Avec cela, nous avons imaginé une structure de signalement de fonctionnalités simple mais puissante.

En bas, nous avons comparé quelques alternatives. Passant d'une solution plus simple et moins flexible à un modèle plus sophistiqué, bien que plus complexe.

Enfin, nous avons brièvement fourni quelques lignes directrices pour construire des solutions plus robustes. Ceci est utile lorsque nous avons besoin d'un degré de granularité supérieur.