正在加载,请稍候…

Feature Flags: Implementation and Management at Scale

Implement and manage feature flags for safe deployments. Learn percentage rollouts, user targeting, A/B testing, and flag cleanup strategies.

Feature Flags: Implementation and Management at Scale

Feature flags (feature toggles) enable trunk-based development and controlled rollouts.

Simple In-Memory Implementation

interface FlagConfig {
  enabled: boolean;
  percentage?: number;      // % of users enabled
  allowedUsers?: string[];  // specific user IDs
  allowedGroups?: string[]; // user groups
}

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;

    // User-specific override
    if (context?.userId && flag.allowedUsers?.includes(context.userId)) return true;

    // Group-based
    if (context?.groups && flag.allowedGroups?.some(g => context.groups?.includes(g))) return true;

    // Percentage rollout (hash-based for consistency)
    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 Integration

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);
}

// Usage in Express middleware
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 Testing

class ABTestingService {
  async getVariant(
    testName: string,
    userId: string
  ): Promise<'control' | 'treatment'> {
    const hash = hashUserId(userId + testName);
    const variant = (hash % 2) === 0 ? 'control' : 'treatment';

    // Track assignment for analysis
    await analytics.track('ab_test_assignment', {
      userId,
      testName,
      variant,
      timestamp: new Date(),
    });

    return variant;
  }
}

// In route handler
const variant = await abTest.getVariant('checkout-button-color', userId);
const buttonColor = variant === 'treatment' ? '#00a651' : '#0066cc';

Flag Cleanup

// Technical debt: flags that should be removed
// Add a TODO with expiry date
const NEW_CHECKOUT = 'new-checkout-flow'; // TODO: remove by 2025-03-01

// Automated stale flag detection
// In CI, check flags older than 30 days
#!/bin/bash
# Find old feature flags in code
grep -r "featureFlags.isEnabled|isFeatureEnabled" src/ |   grep -v "node_modules" |   awk -F"'" '{print $2}' |   sort -u

Environment-Based Flags

// Simple env-var based flags (good for infra/config flags)
const FLAGS = {
  USE_NEW_DB: process.env.USE_NEW_DB === 'true',
  ENABLE_TRACING: process.env.ENABLE_TRACING === 'true',
};

// But prefer a proper flag service for user-facing features

Feature flags require discipline: create them with a plan to remove them.