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
}
}
这个完整的示例演示了:
- 基本设置:初始化OpenFeature API和设置提供者
- 评估上下文:创建包含多种数据类型的详细上下文
- 功能标志评估:获取布尔、整数、字符串等不同类型的标志值
- 错误处理:使用unwrap_or()处理提供者返回的错误
- 条件逻辑:根据功能标志状态执行不同的代码路径
- 测试用例:包含基本的测试示例
注意:实际使用时需要将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!("应用默认功能");
}
最佳实践
- 命名规范:使用小写字母和连字符命名功能标志
- 默认值:始终提供合理的默认值
- 上下文信息:提供足够的上下文信息以确保正确的功能评估
- 错误处理:妥善处理提供商不可用的情况
- 清理旧标志:定期清理不再使用的功能标志
注意事项
- 在生产环境中使用前充分测试功能标志逻辑
- 监控功能标志系统的性能和可靠性
- 建立功能标志的生命周期管理流程
- 确保团队对功能标志的使用有一致的理解
这个库特别适用于需要频繁进行功能发布、A/B测试和灰度发布的项目,能够显著提高发布的灵活性和安全性。