
功能开关:规模化实施与管理
功能开关(Feature Toggles)支持主干开发和控制发布。
简单的内存实现
interface FlagConfig {
enabled: boolean;
percentage?: number; // 启用用户的百分比
allowedUsers?: string[]; // 特定用户ID
allowedGroups?: string[]; // 用户组
}
class FeatureFlagService {
private flags: Map<string, FlagConfig>;
constructor(config: Record<string, FlagConfig>) {
this.flags = new Map(Object.entries(config));
}
isEnabled(flagName: string, context?: { userId?: string; groups?: string[] }): boolean {
const flag = this.flags.get(flagName);
if (!flag || !flag.enabled) return false;
// 用户特定覆盖
if (context?.userId && flag.allowedUsers?.includes(context.userId)) return true;
// 基于组
if (context?.groups && flag.allowedGroups?.some(g => context.groups?.includes(g))) return true;
// 百分比发布(基于哈希以保证一致性)
if (flag.percentage !== undefined && context?.userId) {
const hash = this.hashUserId(context.userId);
return (hash % 100) < flag.percentage;
}
return flag.enabled && !flag.percentage;
}
private hashUserId(userId: string): number {
let hash = 0;
for (const char of userId) {
hash = ((hash << 5) - hash) + char.charCodeAt(0);
hash |= 0;
}
return Math.abs(hash);
}
}

LaunchDarkly 集成
import * as LaunchDarkly from '@launchdarkly/node-server-sdk';
const client = LaunchDarkly.init(process.env.LD_SDK_KEY!);
async function isFeatureEnabled(
flagKey: string,
user: { key: string; email?: string; groups?: string[] }
): Promise<boolean> {
await client.waitForInitialization();
return client.variation(flagKey, {
key: user.key,
email: user.email,
custom: { groups: user.groups },
}, false);
}
// 在 Express 中间件中使用
app.use(async (req, res, next) => {
const userId = req.user?.id ?? 'anonymous';
req.features = {
newCheckout: await isFeatureEnabled('new-checkout-flow', { key: userId }),
mlRecommendations: await isFeatureEnabled('ml-recommendations', { key: userId }),
};
next();
});

A/B 测试
class ABTestingService {
async getVariant(
testName: string,
userId: string
): Promise<'control' | 'treatment'> {
const hash = hashUserId(userId + testName);
const variant = (hash % 2) === 0 ? 'control' : 'treatment';
// 记录分配用于分析
await analytics.track('ab_test_assignment', {
userId,
testName,
variant,
timestamp: new Date(),
});
return variant;
}
}
// 在路由处理器中
const variant = await abTest.getVariant('checkout-button-color', userId);
const buttonColor = variant === 'treatment' ? '#00a651' : '#0066cc';

标志清理
// 技术债务:应移除的标志
// 添加带有过期日期的 TODO
const NEW_CHECKOUT = 'new-checkout-flow'; // TODO: 在 2025-03-01 前移除
// 自动检测过时标志
// 在 CI 中,检查超过 30 天的标志
#!/bin/bash
# 在代码中查找旧的功能标志
grep -r "featureFlags.isEnabled\|isFeatureEnabled" src/ | grep -v "node_modules" | awk -F"'" '{print $2}' | sort -u
基于环境的标志
// 简单的环境变量标志(适用于基础设施/配置标志)
const FLAGS = {
USE_NEW_DB: process.env.USE_NEW_DB === 'true',
ENABLE_TRACING: process.env.ENABLE_TRACING === 'true',
};
// 但对于面向用户的功能,更推荐使用适当的标志服务
功能开关需要纪律:创建时就要计划好何时移除它们。