OpenFeature 标准在 ABP vNext 的落地
🚀 OpenFeature 标准在 ABP vNext 的落地
📚 目录
- 🚀 OpenFeature 标准在 ABP vNext 的落地
-
- 0. 摘要(TL;DR)✨
-
- 🗺️ 架构总览
- 1. 为什么选 OpenFeature 🔧
- 2. 工程脚手架与依赖 📦
- 3. ABP 集成设计(DI & 请求级 Transaction Context)🏗️
-
- 3.1 Program.cs(最小可运行骨架)
- 3.2 请求中间件(设置并清理上下文)
- 🔄 请求生命周期
- 4. flagd 配置与级联覆盖/灰度 🧩
-
- 🎯 目标规则判定
- 5. 本地运行与热更新 🧰
-
- 5.1 Docker 一行命令
- 5.2 Docker Compose(推荐,便于版本化)
- 6. 业务门面(强类型评估 API)🧩
- 7. 高可用:Polly v8 + 分布式缓存 + 默认值 + ABP 兜底 🛡️
-
- 7.1 缓存项用类类型(便于序列化/观测/多租户键)
- 7.2 装饰器:为业务门面叠加韧性与缓存
- 🧭 韧性管线图
- 8. Provider 事件与可观测性(Hook/Tracking/OTel)📈
-
- 8.1 Provider 事件(就绪/错误/配置变化)
- 8.2 Hook 与 Tracking(前置条件⚠️)
- 9. 测试与复现(Testcontainers 起 flagd)🧪
-
- 🧭 CI 流程
- 10. 安全与合规 🔒
- 11. 部署与运维 ☁️
- 12. 常见坑位清单 ⚠️
0. 摘要(TL;DR)✨
在 ABP vNext(.NET 8)中接入 OpenFeature .NET SDK,用统一的评估 API 读取特性开关;后端通过 Provider 对接 flagd / LaunchDarkly / Flipt 等系统,实现可插拔、零侵入。
本方案覆盖:多租户上下文(tenantId/userId/plan/region/targetingKey
)、热更新/灰度、Polly v8 韧性(超时/重试/断路/回退)+ 分布式缓存、事件与可观测性(Hook/Tracking/OTel),并提供可复现的最小代码与 flags.json
/Docker 配置。🧪
🗺️ 架构总览
1. 为什么选 OpenFeature 🔧
-
统一 API,后端可插拔:业务只依赖 OpenFeature;更换平台仅替换 Provider。
-
分工明确:
- 运行时评估/实验/跨生态 → OpenFeature(业务读开关)。
- 治理/权限/后台配置 → ABP Feature(
IFeatureChecker
、[RequiresFeature]
)。
-
生态:开源 flagd(轻量、JsonLogic 规则、确定性分桶)、LaunchDarkly(成熟 SaaS)、Flipt(开源自托管)。🎯
2. 工程脚手架与依赖 📦
目标:.NET 8 + ABP vNext(ASP.NET Core)
dotnet add package OpenFeature
dotnet add package OpenFeature.DependencyInjection # 实验性
dotnet add package OpenFeature.Hosting # 实验性
dotnet add package OpenFeature.Contrib.Providers.Flagd
dotnet add package Polly # v8
dotnet add package Scrutor # 用于 services.Decorate 装饰器
🔔 提示:
OpenFeature.DependencyInjection
/OpenFeature.Hosting
属实验性集成,升级需关注变更说明。
3. ABP 集成设计(DI & 请求级 Transaction Context)🏗️
3.1 Program.cs(最小可运行骨架)
using OpenFeature;
using OpenFeature.DependencyInjection.Providers.Flagd;
using Volo.Abp;
using Volo.Abp.Modularity;var builder = WebApplication.CreateBuilder(args);// 1) OpenFeature + flagd Provider(托管生命周期)
builder.Services.AddOpenFeature(cfg =>
{cfg.AddHostedFeatureLifecycle() // 实验性:由宿主管理 Provider 的初始化/关闭.AddFlagdProvider(o =>{// 可用配置或环境变量 FLAGD_HOST/FLAGD_PORTo.Host = builder.Configuration["Flagd:Host"] ?? "localhost";o.Port = int.Parse(builder.Configuration["Flagd:Port"] ?? "8013");});
});// 2) Transaction Context 传播器(基于 AsyncLocal)
Api.Instance.SetTransactionContextPropagator(new AsyncLocalTransactionContextPropagator());// 3) 请求中间件:构造/设置/清理 EvaluationContext
builder.Services.AddTransient<OpenFeatureContextMiddleware>();// 4) 业务门面与装饰器(Scoped,避免把 Scoped 依赖注入到单例)
builder.Services.AddScoped<IFeatureService, FeatureService>();
builder.Services.Decorate<IFeatureService, SafeFeatureService>(); // 叠加韧性与缓存var app = builder.Build();
app.UseMiddleware<OpenFeatureContextMiddleware>(); // 放在认证之后更佳
app.MapGet("/", () => "ok");
app.Run();
3.2 请求中间件(设置并清理上下文)
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;public class OpenFeatureContextMiddleware : IMiddleware
{private readonly ICurrentTenant _tenant;private readonly ICurrentUser _user;private readonly ILogger<OpenFeatureContextMiddleware> _logger;public OpenFeatureContextMiddleware(ICurrentTenant tenant, ICurrentUser user, ILogger<OpenFeatureContextMiddleware> logger){_tenant = tenant;_user = user;_logger = logger;}public async Task InvokeAsync(HttpContext ctx, RequestDelegate next){var tenantId = _tenant.Id?.ToString() ?? "host";var userId = _user.Id?.ToString() ?? "anonymous";var region = ctx.Request.Headers["X-Region"].FirstOrDefault() ?? "ap-sg";var plan = ctx.Request.Headers["X-Plan"].FirstOrDefault() ?? await ResolveTenantPlanAsync(_tenant);// targetingKey:稳定分桶键(建议:tenantId:userId)var evalCtx = EvaluationContext.Builder().Set("tenantId", tenantId).Set("userId", userId)