正在加载,请稍候…

Feature Flags and Progressive Delivery: LaunchDarkly and Unleash

Use feature flags for safe deployments. Learn canary releases, A/B testing, kill switches, and implementing feature flags with LaunchDarkly or open-source Unleash.

Feature Flags and Progressive Delivery

Why Feature Flags?

Without flags: Deploy -> All users get new feature immediately
With flags:    Deploy -> Control rollout per user/segment

Use cases:
- Canary releases (0% -> 1% -> 10% -> 50% -> 100%)
- A/B testing (measure impact before full rollout)
- Kill switch (disable broken features without redeploy)
- Beta features for specific users/companies
- Dark launches (ship code, enable later)

Simple Implementation

interface FeatureFlag {
  enabled: boolean;
  percentage?: number;
  userIds?: string[];
  segments?: string[];
}

class FeatureFlagService {
  private flags = new Map<string, FeatureFlag>();

  constructor(private redis: RedisClient) {}

  async isEnabled(flagKey: string, user: { id: string; segment?: string }): Promise<boolean> {
    const flag = await this.getFlag(flagKey);
    if (!flag) return false;
    if (!flag.enabled) return false;

    // Specific user override
    if (flag.userIds?.includes(user.id)) return true;

    // Segment-based
    if (flag.segments && user.segment && flag.segments.includes(user.segment)) return true;

    // Percentage rollout (consistent per user)
    if (flag.percentage !== undefined) {
      const hash = parseInt(md5(`${flagKey}:${user.id}`).substring(0, 8), 16);
      return (hash % 100) < flag.percentage;
    }

    return flag.enabled;
  }

  private async getFlag(key: string): Promise<FeatureFlag | null> {
    const cached = await this.redis.get(`flag:${key}`);
    if (cached) return JSON.parse(cached);

    const flag = await this.db.findFlag(key);
    if (flag) await this.redis.setEx(`flag:${key}`, 30, JSON.stringify(flag));
    return flag;
  }
}

// Usage in code
const featureFlags = new FeatureFlagService(redis);

app.get('/checkout', async (req, res) => {
  const newCheckout = await featureFlags.isEnabled('new-checkout-flow', req.user);

  if (newCheckout) {
    return newCheckoutController.handle(req, res);
  }
  return legacyCheckoutController.handle(req, res);
});

OpenFeature Standard

// OpenFeature - vendor-neutral feature flag standard
import { OpenFeature } from '@openfeature/server-sdk';
import { LaunchDarklyProvider } from '@openfeature/launchdarkly-provider';

await OpenFeature.setProvider(new LaunchDarklyProvider(process.env.LD_SDK_KEY!));
const client = OpenFeature.getClient('my-app');

// Evaluation context
const context = { targetingKey: user.id, email: user.email, plan: user.plan };

// Boolean flag
const newDashboard = await client.getBooleanValue('new-dashboard', false, context);

// String flag (multi-variant)
const checkoutVariant = await client.getStringValue('checkout-variant', 'control', context);

// Number flag
const maxItems = await client.getNumberValue('cart-max-items', 10, context);

Canary Release Flow

// Progressive rollout
async function progressiveRollout(flagKey: string): Promise<void> {
  const stages = [1, 5, 10, 25, 50, 100];

  for (const percentage of stages) {
    await flagService.update(flagKey, { percentage });
    console.log(`Rolled out ${flagKey} to ${percentage}%`);

    // Monitor for 10 minutes
    await waitAndMonitor(10 * 60 * 1000, {
      errorRateThreshold: 0.01,    // 1% error rate
      latencyP99Threshold: 500,    // 500ms P99
      onThresholdExceeded: async () => {
        await flagService.update(flagKey, { percentage: 0, enabled: false });
        throw new Error(`Rollback triggered at ${percentage}%`);
      }
    });
  }
}

Feature flags decouple deployment from release - ship code whenever, release features safely.