Rust功能管理库open-feature的使用:实现灵活可扩展的功能标志与实验系统

// 基础示例
async fn example() -> Result<(), Error> {
    // 获取OpenFeature API实例
    // 注意这里的`await`调用,因为使用异步锁来保证线程安全
    let mut api = OpenFeature::singleton_mut().await;

    // 配置提供者
    // 默认使用[`NoOpProvider`]
    api.set_provider(NoOpProvider::default()).await;

    // 创建客户端
    let client = api.get_client();

    // 获取布尔标志值
    let is_feature_enabled = client
        .get_bool_value("v2_enabled", None, None)
        .unwrap_or(false)
        .await;

    Ok(())
}

// 扩展示例
#[tokio::test]
async fn extended_example() {
    // 获取OpenFeature API实例
    let mut api = OpenFeature::singleton_mut().await;

    // 设置默认(未命名)提供者
    api.set_provider(NoOpProvider::default()).await;

    // 创建未命名客户端
    let client = api.create_client();

    // 创建评估上下文
    // 它支持规范中提到的类型
    let evaluation_context = EvaluationContext::default()
        .with_targeting_key("Targeting")
        .with_custom_field("bool_key", true)
        .with_custom_field("int_key", 100)
        .with_custom_field("float_key", 3.14)
        .with_custom_field("string_key", "Hello".to_string())
        .with_custom_field("datetime_key", time::OffsetDateTime::now_utc())
        .with_custom_field(
            "struct_key",
            EvaluationContextFieldValue::Struct(Arc::new(MyStruct::default())),
        )
        .with_custom_field("another_struct_key", Arc::new(MyStruct::default()))
        .with_custom_field(
            "yet_another_struct_key",
            EvaluationContextFieldValue::new_struct(MyStruct::default()),
        );

    // 此函数返回一个`Result`
    // 您可以使用std提供的函数处理它
    let is_feature_enabled = client
        .get_bool_value("SomeFlagEnabled", Some(&evaluation_context), None)
        .await
        .unwrap_or(false);

    if is_feature_enabled {
        // 让我们获取评估详情
        let _result = client
            .get_int_details("key", Some(&evaluation_context), None)
            .await;
    }
}
// 完整示例demo
use open_feature::{OpenFeature, NoOpProvider, EvaluationContext, EvaluationContextFieldValue};
use std::sync::Arc;
use time::OffsetDateTime;

// 自定义结构体
#[derive(Default)]
struct MyStruct {
    field1: String,
    field2: i32,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 获取OpenFeature API单例实例
    let mut api = OpenFeature::singleton_mut().await;

    // 设置功能标志提供者(替换NoOpProvider为实际提供者)
    api.set_provider(NoOpProvider::default()).await;

    // 创建客户端
    let client = api.create_client();

    // 创建详细的评估上下文
    let evaluation_context = EvaluationContext::default()
        .with_targeting_key("user-12345") // 目标键
        .with_custom_field("is_premium", true) // 布尔值
        .with_custom_field("user_age", 28) // 整数值
        .with_custom_field("account_balance", 99.99) // 浮点值
        .with_custom_field("user_email", "user@example.com".to_string()) // 字符串值
        .with_custom_field("signup_date", OffsetDateTime::now_utc()) // 日期时间值
        .with_custom_field(
            "user_profile",
            EvaluationContextFieldValue::new_struct(MyStruct {
                field1: "John Doe".to_string(),
                field2: 100,
            }),
        ); // 结构体值

    // 获取布尔功能标志值
    let is_new_ui_enabled = client
        .get_bool_value("new_ui_enabled", Some(&evaluation_context), None)
        .await
        .unwrap_or(false);

    println!("New UI enabled: {}", is_new_ui_enabled);

    // 根据功能标志状态执行不同逻辑
    if is_new_ui_enabled {
        // 新UI逻辑
        println!("Using new UI version");
        
        // 获取整数功能标志值
        let max_items = client
            .get_int_value("max_display_items", Some(&evaluation_context), None)
            .await
            .unwrap_or(10);
            
        println!("Max display items: {}", max_items);
    } else {
        // 旧UI逻辑
        println!("Using legacy UI version");
    }

    // 获取字符串功能标志值
    let theme_color = client
        .get_string_value("theme_color", Some(&evaluation_context), None)
        .await
        .unwrap_or_else(|_| "blue".to_string());
        
    println!("Theme color: {}", theme_color);

    Ok(())
}

// 测试用例
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_feature_flags() {
        let mut api = OpenFeature::singleton_mut().await;
        api.set_provider(NoOpProvider::default()).await;
        
        let client = api.create_client();
        
        // 测试默认值回退
        let result = client
            .get_bool_value("non_existent_flag", None, None)
            .await
            .unwrap_or(true);
            
        assert!(result); // NoOpProvider总是返回Err,所以使用默认值true
    }
}

这个完整的示例演示了:

  1. 基本设置:初始化OpenFeature API和设置提供者
  2. 评估上下文:创建包含多种数据类型的详细上下文
  3. 功能标志评估:获取布尔、整数、字符串等不同类型的标志值
  4. 错误处理:使用unwrap_or()处理提供者返回的错误
  5. 条件逻辑:根据功能标志状态执行不同的代码路径
  6. 测试用例:包含基本的测试示例

注意:实际使用时需要将NoOpProvider替换为真实的功能标志管理提供者,因为NoOpProvider总是返回错误,仅用于开发和测试目的。


1 回复

Rust功能管理库open-feature的使用:实现灵活可扩展的功能标志与实验系统

概述

open-feature是一个功能标志管理库,允许开发者在运行时动态控制功能开关,实现功能发布、A/B测试和渐进式发布等场景。该库提供标准化的API,支持多种后端提供商,包括LaunchDarkly、Split等。

核心概念

  • 功能标志(Feature Flag):布尔值或配置项,控制功能是否启用
  • 评估上下文(Evaluation Context):包含用户、环境等属性的键值对
  • 提供商(Provider):具体实现功能标志评估逻辑的后端服务

安装方法

在Cargo.toml中添加依赖:

[dependencies]
openfeature = "0.5"

基本用法

1. 初始化客户端

use openfeature::{
    client::Client, 
    provider::FeatureProvider, 
    evaluation_context::EvaluationContext,
    value::Value
};

// 创建默认客户端
let client = Client::new();

2. 布尔型功能标志

// 检查功能是否启用
let is_enabled = client.get_bool_value("new-checkout-flow", false, &EvaluationContext::default());

if is_enabled {
    println!("使用新版结账流程");
} else {
    println!("使用旧版结账流程");
}

3. 带上下文的功能评估

let mut context = EvaluationContext::default()
    .with_targeting_key("user-123")
    .with_attribute("tier", Value::String("premium".into()))
    .with_attribute("country", Value::String("US".into()));

let should_show_feature = client.get_bool_value(
    "premium-feature", 
    false, 
    &context
);

4. 多变量功能标志

// 获取字符串值
let theme = client.get_string_value(
    "ui-theme", 
    "light", 
    &EvaluationContext::default()
);

// 获取整数值
let max_results = client.get_int_value(
    "search-max-results", 
    10, 
    &EvaluationContext::default()
);

// 获取结构化数据
let config = client.get_object_value(
    "search-config",
    Value::Null,
    &EvaluationContext::default()
);

5. A/B测试示例

fn handle_user_request(user_id: &str, country: &str) {
    let context = EvaluationContext::default()
        .with_targeting_key(user_id)
        .with_attribute("country", Value::String(country.into()));
    
    let variant = client.get_string_value(
        "pricing-experiment",
        "control",
        &context
    );
    
    match variant.as_str() {
        "control" => apply_standard_pricing(),
        "variant_a" => apply_discounted_pricing(0.1),
        "variant_b" => apply_premium_pricing(),
        _ => apply_standard_pricing()
    }
}

高级配置

自定义提供商

use openfeature::provider::{FeatureProvider, ResolutionDetails};

struct CustomProvider;

impl FeatureProvider for CustomProvider {
    fn get_metadata(&self) -> Metadata {
        Metadata::new("custom-provider")
    }
    
    fn resolve_bool_value(
        &self,
        flag_key: &str,
        context: &EvaluationContext,
    ) -> ResolutionDetails<bool> {
        // 自定义逻辑
        ResolutionDetails::new(true)
    }
}

// 设置自定义提供商
client.set_provider(Box::new(CustomProvider));

异步评估

use openfeature::client::AsyncClient;

#[tokio::main]
async fn main() {
    let async_client = AsyncClient::new();
    
    let result = async_client.get_bool_value(
        "async-feature",
        false,
        &EvaluationContext::default()
    ).await;
}

完整示例demo

use openfeature::{
    client::Client,
    evaluation_context::EvaluationContext,
    value::Value,
    provider::{FeatureProvider, ResolutionDetails, Metadata}
};

// 自定义提供商实现
struct LocalFileProvider;

impl FeatureProvider for LocalFileProvider {
    fn get_metadata(&self) -> Metadata {
        Metadata::new("local-file-provider")
    }
    
    fn resolve_bool_value(
        &self,
        flag_key: &str,
        context: &EvaluationContext,
    ) -> ResolutionDetails<bool> {
        // 模拟从本地文件读取配置
        match flag_key {
            "new-ui-enabled" => ResolutionDetails::new(true),
            "beta-feature" => {
                // 根据用户属性决定是否启用
                if let Some(Value::String(tier)) = context.get_attribute("tier") {
                    if tier == "premium" {
                        return ResolutionDetails::new(true);
                    }
                }
                ResolutionDetails::new(false)
            }
            _ => ResolutionDetails::new(false)
        }
    }
    
    fn resolve_string_value(
        &self,
        flag_key: &str,
        context: &EvaluationContext,
    ) -> ResolutionDetails<String> {
        match flag_key {
            "theme-color" => ResolutionDetails::new("blue".to_string()),
            "pricing-tier" => {
                if let Some(Value::String(country)) = context.get_attribute("country") {
                    if country == "US" {
                        return ResolutionDetails::new("premium".to_string());
                    }
                }
                ResolutionDetails::new("standard".to_string())
            }
            _ => ResolutionDetails::new("default".to_string())
        }
    }
}

fn main() {
    // 创建客户端并设置自定义提供商
    let client = Client::new();
    client.set_provider(Box::new(LocalFileProvider));
    
    // 示例1: 布尔型功能标志
    println!("=== 布尔型功能标志示例 ===");
    let new_ui_enabled = client.get_bool_value(
        "new-ui-enabled", 
        false, 
        &EvaluationContext::default()
    );
    println!("新UI功能启用状态: {}", new_ui_enabled);
    
    // 示例2: 带上下文的评估
    println!("\n=== 带上下文的评估示例 ===");
    let user_context = EvaluationContext::default()
        .with_targeting_key("user-456")
        .with_attribute("tier", Value::String("premium".into()))
        .with_attribute("country", Value::String("US".into()));
    
    let beta_access = client.get_bool_value(
        "beta-feature",
        false,
        &user_context
    );
    println!("Beta功能访问权限: {}", beta_access);
    
    // 示例3: 字符串型功能标志
    println!("\n=== 字符串型功能标志示例 ===");
    let user_theme = client.get_string_value(
        "theme-color",
        "default",
        &EvaluationContext::default()
    );
    println!("用户主题颜色: {}", user_theme);
    
    // 示例4: A/B测试场景
    println!("\n=== A/B测试示例 ===");
    let pricing_tier = client.get_string_value(
        "pricing-tier",
        "standard",
        &user_context
    );
    println!("定价层级: {}", pricing_tier);
    
    // 根据功能标志执行不同逻辑
    match pricing_tier.as_str() {
        "premium" => apply_premium_features(),
        "standard" => apply_standard_features(),
        _ => apply_default_features()
    }
}

// 示例函数
fn apply_premium_features() {
    println!("应用高级功能");
}

fn apply_standard_features() {
    println!("应用标准功能");
}

fn apply_default_features() {
    println!("应用默认功能");
}

最佳实践

  1. 命名规范:使用小写字母和连字符命名功能标志
  2. 默认值:始终提供合理的默认值
  3. 上下文信息:提供足够的上下文信息以确保正确的功能评估
  4. 错误处理:妥善处理提供商不可用的情况
  5. 清理旧标志:定期清理不再使用的功能标志

注意事项

  • 在生产环境中使用前充分测试功能标志逻辑
  • 监控功能标志系统的性能和可靠性
  • 建立功能标志的生命周期管理流程
  • 确保团队对功能标志的使用有一致的理解

这个库特别适用于需要频繁进行功能发布、A/B测试和灰度发布的项目,能够显著提高发布的灵活性和安全性。

回到顶部