
在现代生产系统中,特性标志(Feature Flags)不是简单的开关——它们是实现敏捷性、安全性和实验能力的战略杠杆。但开发者常像"在地下室开关电灯"般粗暴实现它们。
本文通过 .NET 实现健壮、可扩展、可测试的特性标志架构: ✳️ Minimal APIs 🧱 策略模式 🧪 上下文标志解析 🧩 DI 驱动架构 若你曾说过"加个 if 语句就行",请立即阅读本文,避免应用陷入条件语句地狱!
我们常见到这种导致维护噩梦的混乱模式,在 Minimal API 中尤为刺眼:
app.MapGet("/checkout", async (HttpContext ctx) => {
var flagService = ctx.RequestServices.GetRequiredService<IFeatureFlagProvider>();
if (await flagService.IsEnabledAsync("UseNewCheckout"))
return Results.Ok("Using new checkout!");
else
return Results.Ok("Using legacy checkout.");
});❌ 致命缺陷:
HttpContext原则 | 解决痛点 | 关键收益 |
|---|---|---|
🔁 行为解耦 | 标志条件侵入业务逻辑 | 核心逻辑纯净可读 |
🧱 策略模式 | 硬编码分支 | 动态注入实现版本切换 |
🌍 上下文感知 | 全局开关缺乏精细控制 | 租户/环境/用户级灰度发布 |
🧪 易于测试 | 模拟复杂依赖链 | 秒级单元测试 |
⚙️ 组合式解析逻辑 | 复杂规则难以实现 | 多条件智能策略 |
🧯 故障安全默认值 | 标志服务宕机导致系统崩溃 | 自动降级保核心功能 |
业务需求:
仅租户 abc 启用实时采集用户数据零部署秒级回滚通过策略模式架构实现:精准控制 + 实时洞察 + 安全回滚
public interfaceIFeatureFlagProvider
{
/// <summary>
/// 基于上下文判断特性是否启用
/// </summary>
Task<bool> IsEnabledAsync(string flagName, FlagContext context);
}
publicclassFlagContext
{
publicstring? TenantId { get; set; } // 多租户支持
publicstring? Environment { get; set; } // 环境隔离
// 可扩展:用户ID/国家代码/浏览器类型等
}public interfaceICheckoutStrategy
{
Task<IResult> ProcessCheckoutAsync();
}
publicclassNewCheckoutStrategy : ICheckoutStrategy
{
public Task<IResult> ProcessCheckoutAsync() =>
Task.FromResult(Results.Ok("🚀 新结算系统处理"));
}
publicclassLegacyCheckoutStrategy : ICheckoutStrategy
{
public Task<IResult> ProcessCheckoutAsync() =>
Task.FromResult(Results.Ok("🏷️ 旧结算系统处理"));
}public classCheckoutStrategyFactory
{
privatereadonly IServiceProvider _provider;
privatereadonly IFeatureFlagProvider _flagProvider;
privatereadonly ILogger _logger;
public async Task<ICheckoutStrategy> ResolveAsync(FlagContext context)
{
try
{
bool useNew = await _flagProvider.IsEnabledAsync("UseNewCheckout", context);
return useNew
? _provider.GetService<NewCheckoutStrategy>()
: _provider.GetService<LegacyCheckoutStrategy>();
}
catch (Exception ex) // 故障安全降级
{
_logger.LogError(ex, "标志解析失败,降级至旧系统");
return _provider.GetService<LegacyCheckoutStrategy>();
}
}
}// Program.cs
builder.Services.AddSingleton<NewCheckoutStrategy>();
builder.Services.AddSingleton<LegacyCheckoutStrategy>();
builder.Services.AddSingleton<CheckoutStrategyFactory>();
app.MapPost("/checkout", async (HttpContext ctx, CheckoutStrategyFactory factory, IWebHostEnvironment env) =>
{
var context = new FlagContext {
TenantId = ctx.Request.Headers["X-Tenant-Id"],
Environment = env.EnvironmentName
};
var strategy = await factory.ResolveAsync(context);
return await strategy.ProcessCheckoutAsync();
});public class FakeFlagProvider : IFeatureFlagProvider
{
private readonly Dictionary<string, bool> _flags = new();
public Task<bool> IsEnabledAsync(string featureName, FlagContext context)
=> Task.FromResult(_flags[featureName]);
}[Fact]
publicasync Task 新标志开启时使用新系统()
{
// 构造测试环境
var services = new ServiceCollection()
.AddSingleton<NewCheckoutStrategy>()
.AddSingleton<LegacyCheckoutStrategy>()
.BuildServiceProvider();
// 模拟标志状态
var fakeFlags = new FakeFlagProvider("UseNewCheckout", true);
// 执行解析
var factory = new CheckoutStrategyFactory(services, fakeFlags, NullLogger.Instance);
var strategy = await factory.ResolveAsync(new FlagContext());
// 验证结果
var result = await strategy.ProcessCheckoutAsync();
Assert.Equal("🚀 新结算系统处理", ((Ok<string>)result).Value);
}方案 | 适用场景 | 特点 |
|---|---|---|
LaunchDarkly | 企业级复杂灰度 | 实时定位/实验分析 |
Azure App Configuration | Azure 生态集成 | 动态更新/零代码部署 |
Redis 存储方案 | 高性能分布式系统 | 亚毫秒级标志更新 |
数据库方案 | 全自定义需求 | 完全控制审计流 |
app.Use(async (ctx, next) =>
{
var flagProvider = ctx.RequestServices.GetService<IFeatureFlagProvider>();
var context = new FlagContext {
TenantId = ctx.Request.Headers["X-Tenant-Id"],
Environment = env.EnvironmentName
};
if (!await flagProvider.IsEnabledAsync("BetaAccess", context))
{
ctx.Response.StatusCode = ;
await ctx.Response.WriteAsync("⛔ 您无权访问Beta功能");
return;
}
await next();
});能力 | 业务价值 |
|---|---|
🧪 A/B 测试 | 数据驱动功能迭代 |
🏢 多租户精准控制 | SaaS 客户定制体验 |
☁️ 云原生动态更新 | 无需重启修改功能 |
🧩 模块化插件管理 | 运行时热插拔组件 |
⛔ 秒级熔断 | 生产事故分钟级恢复 |
某 SaaS 团队实践成果:
终极洞见: 特性标志架构不是开关管理,而是业务敏捷性的核心引擎!
点击下方卡片关注DotNet NB