ENbext.dev
DocumentationFonctionnalités

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Indicateur de fonctionnalité

La fonctionnalité de l'indicateur de fonctionnalité répond à une question à chaque appel : , quelle valeur l'indicateur X doit-il prendre pour cet appelant particulier, à l'heure actuelle ? Un FeatureFlagPlugin est un fournisseur — donnez-lui une clé d'indicateur et un contexte, il renvoie une valeur saisie. Le trait ne dit rien sur la façon dont la valeur a été produite : elle peut provenir d'un fichier TOML fourni avec le site, du SDK OpenFeature encapsulant un fournisseur, ou d'un SDK direct comme LaunchDarkly ou Unleash. Tous les trois se trouvent derrière la même forme, de sorte que les sites d'appels ne se branchent jamais sur le backend.

Cette page couvre le trait lui-même, l'ABI FlagValue, pourquoi un indicateur manquant n'est pas une erreur, comment fonctionnent les déploiements déterministes par utilisateur et comment les deux plugins de référence – @bext/flags-static et @bext/flags-openfeature – correspondent à la forme.

Le trait#

Chaque plugin Feature Flag implémente un trait, FeatureFlagPlugin, défini dans bext-plugin-api::feature_flag :

rust
pub trait FeatureFlagPlugin: Send + Sync {
    fn name(&self) -> &str;

    fn evaluate(&self, key: &str, ctx: &FlagContext) -> Result<FlagValue, String>;

    fn evaluate_batch(
        &self,
        keys: &[&str],
        ctx: &FlagContext,
    ) -> Result<HashMap<String, FlagValue>, String> { /* default walks evaluate */ }

    fn refresh(&self) -> Result<(), String> { Ok(()) }
    fn is_healthy(&self) -> bool { true }
}

Le trait est synchronisé pour correspondre à tous les autres traits de bext-plugin-api, de sorte que la forme se déplace proprement à travers les bacs à sable WASM, QuickJS et nsjail dans lesquels les plugins peuvent s'exécuter. Les backends qui ont besoin d'E/S réseau (fournisseurs de SDK OpenFeature, LaunchDarkly, Unleash) utilisent un client bloquant ou encapsulent un client asynchrone dans un environnement d'exécution de la même manière que le récupérateur JWKS dans @bext/auth-jwt. Le moteur d'exécution n'expose pas l'asynchrone au-delà de la limite du bac à sable, de sorte que la forme orientée vers l'hôte reste synchronisée.

evaluate_batch a une implémentation par défaut qui parcourt evaluate une touche à la fois. Les backends avec des API par lots natives (variationAll de LaunchDarkly, liste ProviderEvaluation d'OpenFeature, getAllGates de Statsig) le remplacent pour plus d'efficacité. refresh permet aux backends locaux de relire leur fichier de configuration hors du disque et aux backends distants d'extraire un nouvel instantané ; le runtime planifie lui-même refresh, les plugins n'ont donc pas besoin de générer des observateurs en arrière-plan.

FlagValue — quatre formes typées, ABI plat#

rust
pub enum FlagValue {
    Bool(bool),
    String(String),
    Int(i64),
    Json(String),
}

Chaque fournisseur de drapeaux suivi par le plan – OpenFeature, LaunchDarkly, Unleash, Statsig, Flagsmith – s'inscrit dans ces quatre formes sans qu'aucune d'entre elles ne soit spéciale. Json transporte un String codé en JSON plutôt qu'un serde_json::Value, de sorte que l'ensemble de l'énumération traverse la limite WASM sous forme d'octets bruts ; si un appelant souhaite un accès structuré, il analyse la chaîne de son propre côté. Il s'agit de la même astuce que la fonctionnalité Session utilise pour transporter les données de session et que la fonctionnalité Lifecycle utilise pour transporter les charges utiles d'événements : pousser l'étape serde vers les bords maintient l'ABI de la fonction hôte petit et identique dans chaque environnement d'exécution sandbox.

Des accesseurs pratiques — FlagValue::as_bool(), .as_str(), .as_int() — existent pour le cas courant de lecture d'un indicateur en ligne sur un site d'appel. as_bool() renvoie un simple bool ( true uniquement pour Bool(true) ) ; as_str() et as_int() renvoient Option afin que les appelants décident si une incompatibilité de type est une contrainte et une poursuite ou un retour aux valeurs par défaut.

FlagContext — ciblage sans couplage de fournisseurs#

rust
pub struct FlagContext {
    pub user_id: Option<String>,
    pub attributes: HashMap<String, String>,
}

user_id est l’identifiant de sujet stable. Les fournisseurs qui effectuent des déploiements ou des regroupements par utilisateur le lisent directement ; fournisseurs qui ne l’ignorent pas. None signifie évaluation anonyme — les fournisseurs qui exigent un sujet pour leurs règles de ciblage doivent revenir à leur valeur par défaut configurée, sans jamais se tromper. La sémantique anonyme explicite permet de charger rapidement les pages déconnectées sans casse spéciale sur les sites d'appel.

attributes est la carte de ciblage de forme libre : pays, niveau de forfait, classe d'appareil, identifiant de cohorte, compartiment d'expérimentation, n'importe quoi. Les clés sont définies par le fournisseur et les valeurs sont des chaînes simples, de sorte que l'ensemble de la carte survit à toute limite de sérialisation. Si vous souhaitez une structure typée ici, vous insérez une forme spécifique au fournisseur dans le trait et la réponse est un évaluateur personnalisé au-dessus du plugin, pas un FlagContext plus riche.

Les indicateurs manquants ne sont pas des erreurs#

evaluate renvoie Result<FlagValue, String>, mais un indicateur qui n'existe pas dans le backend est et non et Err. Les backends renvoient leur variante par défaut configurée pour la clé inconnue : FlagValue::Bool(false) pour les fournisseurs locaux, la « variante par défaut » du fournisseur pour les SDK de style OpenFeature. Cela correspond à la spécification OpenFeature et évite aux appelants d'avoir à distinguer « drapeau non trouvé » de « drapeau désactivé » sur chaque site d'appel, ce qui n'est presque jamais la distinction qu'ils souhaitent réellement. Un Err n'apparaît que lorsque le fournisseur lui-même est en panne : réseau en panne, fichier de configuration mal formé, type de valeur inconnu. C’est le cas intéressant : vous voulez que ce soit bruyant.

Les déploiements par utilisateur doivent être déterministes#

Un appelant évaluant le même indicateur avec le même identifiant utilisateur doit voir la même variante à chaque appel, dans chaque sandbox, dans chaque processus. Sinon, deux panneaux sur la même page peuvent être en désaccord et l'observabilité est rompue. Le backend de référence @bext/flags-static y parvient de la manière standard : il hache user_id + flag_key et prend le résultat modulo 100, puis le compare au rollout_pct configuré. Le hachage dépend uniquement des entrées, la réponse est donc stable entre les redémarrages, les processus et les machines. Un utilisateur inclus dans le déploiement à 10 % est également dans le déploiement à 11 %, le déploiement à 12 %, etc. – aucun utilisateur ne revient en arrière à mesure que les pourcentages augmentent. Il s’agit de la propriété de monotonie qui permet d’accélérer les déploiements en toute sécurité.

Les backends distants héritent de la même propriété de leur fournisseur : les fournisseurs OpenFeature avec targeting_key définis se comportent de la même manière ; Le regroupement de déploiement de LaunchDarkly est basé sur le même identifiant de sujet. Le trait porte spécifiquement user_id dans FlagContext afin que chaque backend puisse fournir cette garantie sans la réinventer.

Plugins de référence#

@ bext /flags-statiques#

Vit à crates/bext-impls/bext-flags-static. Charge un fichier de configuration TOML ou JSON lors de la construction :

toml
[flags.new_checkout]
kind = "bool"
default = false
rollout_pct = 25
rollout = true

[flags.variant]
kind = "string"
default = "control"

[flags.batch_size]
kind = "int"
default = 128

[flags.pricing]
kind = "json"
default = { region = "us", tiers = [9, 29, 99] }

Chaque drapeau a un kind ( bool, string, int ou json ) et un default. Les indicateurs avec un rollout_pct nécessitent également une valeur rollout du même type ; l'évaluation sélectionne la variante si le compartiment de l'utilisateur tombe en dessous du pourcentage, sinon la valeur par défaut. Les appelants anonymes ne bénéficient jamais de la variante de déploiement : l'absence de user_id signifie qu'il n'y a rien à ajouter. refresh() relit le fichier à partir du disque, donc un déploiement qui réécrit la configuration en place récupère les nouvelles valeurs lors de la prochaine actualisation.

Le backend statique est le bon choix lorsque l'état du drapeau est versionné avec le code : vous voulez la même configuration à chaque fois qu'un commit donné est exécuté, et vous voulez que les déploiements soient reproductibles lors du transfert sans service de drapeau séparé.

@ bext /flags-openfeature#

Vit à crates/bext-impls/bext-flags-openfeature. Encapsule un open_feature::provider::FeatureProvider — le trait de fournisseur officiel du SDK Rust — derrière la synchronisation FeatureFlagPlugin. Il possède un petit runtime tokio multithread et appelle block_on sur les méthodes de résolution asynchrone du fournisseur. Il s'agit du même modèle que @bext/auth-jwt utilise pour son outil de récupération JWKS : le côté plugin est synchronisé, il traverse donc proprement les limites du bac à sable ; le côté runtime réside à l’intérieur du plugin et est invisible pour les appelants.

L'inférence de forme parcourt les méthodes de résolution typées du fournisseur dans l'ordre : resolve_bool_value, puis resolve_int_value, puis resolve_string_value. Le premier qui renvoie Ok gagne ; une erreur TypeMismatch signifie "pas cette forme, essayez la suivante". FlagNotFound revient à FlagValue::Bool(false) afin que les indicateurs manquants correspondent à la sémantique OpenFeature sans apparaître sous forme d'erreurs. Cette astuce maintient l'adaptateur indépendant des indicateurs définis dans le fournisseur - vous n'avez pas besoin d'indiquer à l'avance à l'adaptateur que new_checkout est un booléen et batch_size est un entier.

Étant donné que l'adaptateur prend n'importe quel Arc<dyn FeatureProvider>, vous pouvez le pointer vers le fournisseur OpenFeature de LaunchDarkly, Flagsmith, Split ou un fournisseur en cours que vous avez écrit vous-même. La même suite de tests fournie avec la caisse utilise un petit fournisseur de mémoire lancé à la main pour effectuer l'aller-retour.

Configuration#

Les plugins Flag sont configurés dans le bloc [flags] de bext.config.toml :

toml
[flags]
provider = "@bext/flags-static"
config_path = "./flags.toml"
refresh_interval_sec = 60

Pour OpenFeature :

toml
[flags]
provider = "@bext/flags-openfeature"
backend = "launchdarkly"      # or "flagsmith", "split", "unleash", ...
sdk_key = "$LAUNCHDARKLY_SDK_KEY"

Un seul provider est actif à la fois. Les sites qui souhaitent un comportement de secours (valeurs par défaut statiques lorsque le fournisseur distant est en panne) composent les fournisseurs dans le code plutôt que dans la configuration, de sorte que la règle de composition est explicite et le secours peut être récupéré.

Où ça correspond#

Les indicateurs de fonctionnalité sont la fonctionnalité de niveau intermédiaire qui débloque une livraison progressive sur le reste de la pile : un site peut activer une nouvelle route, un nouveau composant, un nouveau gestionnaire d'API ou une nouvelle requête de base de données sur un indicateur sans toucher au pipeline middleware ou à la couche de cache. Le trait s'arrête intentionnellement à « donnez-moi une valeur » ; Les workflows de répartition du trafic, de promotion Canary, d'analyse des expériences et de nettoyage des indicateurs résident au-dessus et non à l'intérieur. Cette répartition est ce qui permet d’ajouter des fonctionnalités peu coûteuses et d’assurer l’interchangeabilité du paysage des fournisseurs.

Conseil

Les indicateurs de fonctionnalités décident quel code exécute ; si vous avez également besoin de mesurer quelle variante est la plus performante, associez cette fonctionnalité à Expérimentation, qui ajoute l'exposition et l'enregistrement des résultats en plus de la même idée de regroupement déterministe.

Remarque

@bext/flags-static appelle refresh() selon un planning géré par l'exécution : les plugins n'ont pas besoin de générer leurs propres observateurs en arrière-plan. L'intervalle est défini avec refresh_interval_sec dans bext.config.toml.

En rapport#

Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.