前言
我一直觉得事情可以保持简单就好,有问题改代码或环境变量切换就好,何必导入更复杂的包管理与第三方服务呢?但真实情况下当问题发生时不会有空闲慢慢部署与排错,就有必要通过更完善的 Feature Flag 规划来降低推送功能的风险,也能减少心脏病发作的机率。
所以透过这篇文章更进一步探讨如何透过 OpenFeature,一款开源的 Feature Flag 实践标准、生态与 SDK 工具打造更稳定顺畅的产品功能推送体验。
现有问题
- 开发已经完成但商业端还没
- 功能分布于多个服务上
- 实验性质功能
通常实现 Feature Flag 只需要在代码当中埋藏的一个变量或环境变量,虽然简单可行但很难管理与追踪,更没办法灵活快速的切换应对复杂突发情境。
const isJira1234FeatureFlagOn = import.meta.env.VITE_Jira_1234if (isJira1234FeatureFlagOn) { // Flag on} else { // Flag off}什么是 OpenFeature?
OpenFeature 是开源且支持多种供应商的开放标准,用于实践通用的 Feature Flag:
OpenFeature 词汇表
- Evaluation API
- 允许开发者评估 Feature Flag,并使用评估结果来影响控制流程或功能。
- Evaluation Context
- 一个容器用于存放任意上下文数据(如:
user_id,region,email),这些数据可用作动态评估的基础。
- 一个容器用于存放任意上下文数据(如:
- Providers
- 负责将提供给 Evaluation API 的参数对应到其在关联标志管理系统中的等效表示形式。Provider 可以封装供应商 SDK、调用自定义的标志评估 REST API,甚至解析本地存储的文件来解析标志值。
- Hooks
- 用于在生命周期的各个阶段添加任意行为。
- Events
- 当系统触发某些特定事件(如:Provider 已就绪、旗标设置变更、Provider 出现故障)时触发。这让应用程序能对环境变化做出即时反应。
如何使用 OpenFeature?
使用 OpenFeature 的过程就像是「配置插座」与「接入插头」的过程,主要分为三个步骤:
一、配置 Provider(设置插座)
import { OpenFeature, InMemoryProvider } from '@openfeature/web-sdk';
const provider = new InMemoryProvider(flagConfig);await OpenFeature.setContext({ user_id: '123' });await OpenFeature.setProviderAndWait(provider);const flagConfig = { 'show-welcome-banner': { disabled: false, variants: { on: true, off: false }, defaultVariant: 'on', // 可選: contextEvaluator 決定 flag 結果基於 context // InMemoryProvider 特有的功能,其他 Provider 通常在服務端處理 contextEvaluator: (ctx: any) => { if (ctx.user_id === '123') return 'on'; return 'off'; } }, 'button-color': { disabled: false, variants: { green: 'green', red: 'red', gray: 'gray' }, defaultVariant: 'green', contextEvaluator: (ctx: any) => 'green' }, 'retry-count': { disabled: false, variants: { five: 5, ten: 10 }, defaultVariant: 'five' }, 'config-object': { disabled: false, variants: { production: { env: 'production', apis: ['v1', 'v2'], }, dev: { env: 'development', apis: ['v1'], } }, defaultVariant: 'production' },};二、获取 Client(取得访问接口)
Client 是与 Feature Flag 互动的入口,可以针对不同的业务逻辑(如:user-service 或 billing)获取不同的 Client。
const client = OpenFeature.getClient('my-app-name');三、执行评估(获取结果)
使用 Client 提供的 API 来获取旗标值。你需要提供旗标名称、默认值以及选填的评估上下文。
// 动态提供上下文(如用户 ID)const context = { targetingKey: 'user-123', region: 'asia' };
// 评估布林类型的旗标const isNewFeatureEnabled = await client.getBooleanValue( 'new-cool-feature', false, context);
if (isNewFeatureEnabled) { // 执行新功能代码}挑选合适的方案
前面使用默认官方实践的 InMemoryProvider web/sdk 于前端用于熟悉 OpenFeature 的大致运作逻辑,具体实践中可以找许多现成的 Provider
也可以在先前基础上接上后端的 flagConfig 用于动态改变 Feature Flag:
// Node.js/Expressapp.get('/api/feature-flags', authenticate, (req, res) => { const userId = req.user.id; const userRegion = req.user.region;
const flags = { 'show-welcome-banner': { disabled: false, variants: { on: true, off: false }, defaultVariant: userId === '123' ? 'on' : 'off' } };
res.json(flags);});const response = await fetch('/api/feature-flags', { headers: { 'Authorization': `Bearer ${token}` }});const flagConfig = await response.json();
const provider = new InMemoryProvider(flagConfig);await OpenFeature.setProviderAndWait(provider);虽然以上的做法可行,但通常 InMemoryProvider 主要用于测试或静态配置,最好采用其他 Feature Flag 设施,例如 flagd与管理界面如 unleash。
// 2. 前端使用 flagd providerimport { FlagdWebProvider } from '@openfeature/flagd-web-provider';
const provider = new FlagdWebProvider({ host: 'https://your-flagd-server.com', port: 8013});
await OpenFeature.setProviderAndWait(provider);延伸阅读
- OpenFeature Will CHANGE How You Deploy Code - Better Stack
- An Introduction to Feature Flagging & OpenFeature - CNCF [Cloud Native Computing Foundation]
- Move from Environment Variables to Feature Flags - OpenFeature