Déployer un vendredi soir ? Avec les Feature Flags, c’est possible. Voici comment nous avons éliminé 90% du stress de déploiement.

Le problème traditionnel

Déploiement = Release

git push → CI/CD → Deploy prod → 🤞

Si bug : rollback complet
        → redéploy entier
        → 15-30 minutes downtime

Résultat :

  • Déploiements le mardi matin uniquement
  • Freeze 2 jours avant weekend
  • Stress maximum

Avec Feature Flags

git push → CI/CD → Deploy prod (feature OFF)
                → Test interne (feature ON pour admins)
                → Rollout 5% users
                → 100% users

Si bug : Toggle flag OFF (instantané)

Résultat :

  • Deploy n’importe quand
  • Rollback < 1 seconde
  • Zero stress

Types de Feature Flags

1. Release Flags (temporaires)

// Nouvelle feature en développement
if (featureFlags.isEnabled('new-checkout-flow')) {
  return <NewCheckoutFlow />;
}
return <OldCheckoutFlow />;

// Supprimer le flag après rollout complet (2-4 semaines)

Usage : Deploy code incomplet en prod.

2. Ops Flags (permanents)

// Kill switch en cas d'incident
if (featureFlags.isEnabled('enable-recommendations')) {
  return await fetchRecommendations();  // Coûteux
}
return [];  // Fallback si service recommendations down

Usage : Circuit breakers, kill switches.

3. Experiment Flags (A/B testing)

// A/B test sur pricing
const variant = featureFlags.getVariant('pricing-test');

if (variant === 'premium') {
  return <PremiumPricing />;
} else if (variant === 'standard') {
  return <StandardPricing />;
}

Usage : Optimisation produit.

4. Permission Flags

// Features par tier abonnement
if (user.tier === 'premium' && featureFlags.isEnabled('advanced-analytics')) {
  return <AdvancedAnalytics />;
}

Usage : Monétisation, beta testing.

Architecture Feature Flags

Setup simple (localStorage)

// feature-flags.ts
export class FeatureFlags {
  private flags: Record<string, boolean> = {
    'new-dashboard': false,
    'dark-mode': true,
  };

  isEnabled(flag: string): boolean {
    // Override local pour dev
    const localOverride = localStorage.getItem(`flag:${flag}`);
    if (localOverride !== null) {
      return localOverride === 'true';
    }

    return this.flags[flag] ?? false;
  }
}

export const featureFlags = new FeatureFlags();

Pros : Simple, zero dépendance Cons : Pas de rollout progressif, pas de targeting

Setup avancé (LaunchDarkly, Unleash)

// feature-flags.ts
import { LDClient } from 'launchdarkly-js-client-sdk';

export class FeatureFlags {
  private client: LDClient;

  async init() {
    this.client = LDClient.initialize('sdk-key', {
      key: user.id,
      email: user.email,
      custom: {
        tier: user.subscriptionTier,
        country: user.country
      }
    });

    await this.client.waitForInitialization();
  }

  isEnabled(flag: string): boolean {
    return this.client.variation(flag, false);
  }

  getVariant(flag: string): string {
    return this.client.variation(flag, 'control');
  }
}

Pros : Rollout progressif, targeting, analytics Cons : Coût (LaunchDarkly : $50+/user/mois)

Setup open-source (Unleash)

// Unleash : Self-hosted, gratuit
import { UnleashClient } from 'unleash-proxy-client';

const unleash = new UnleashClient({
  url: 'https://unleash.mycompany.com/proxy',
  clientKey: 'proxy-secret',
  appName: 'my-app',
});

await unleash.start();

if (unleash.isEnabled('new-feature')) {
  // Feature code
}

Pros : Gratuit, open-source, self-hosted Cons : Infrastructure à maintenir

Rollout progressif : La stratégie

Étape 1 : Admins only (Jour 0)

featureFlags.configure('new-dashboard', {
  enabled: true,
  targeting: {
    userIds: ['admin1', 'admin2']
  }
});

Objectif : Valider la feature en prod réel.

Étape 2 : Équipe interne (Jour 1-2)

targeting: {
  custom: {
    email: { endsWith: '@mycompany.com' }
  }
}

Objectif : Dogfooding, récolter feedback.

Étape 3 : Beta users (Jour 3-5)

targeting: {
  custom: {
    betaTester: true
  }
},
rollout: 100  // 100% des beta testers

Objectif : Valider sur early adopters.

Étape 4 : Canary (Jour 6-10)

rollout: 5  // 5% de tous les users

Objectif : Détecter bugs avec vraie charge.

Monitoring critique :

  • Error rate < 0.1%
  • Latency p95 < baseline +10%
  • Conversions maintenues

Étape 5 : Rollout progressif (Jour 11-20)

Jour 11 : 10%
Jour 13 : 25%
Jour 15 : 50%
Jour 17 : 75%
Jour 20 : 100%

Critère d’avancement : Toutes métriques OK pendant 48h.

Étape 6 : Cleanup (Jour 30)

// Supprimer le flag du code
- if (featureFlags.isEnabled('new-dashboard')) {
-   return <NewDashboard />;
- }
- return <OldDashboard />;
+ return <NewDashboard />;

Important : Ne pas accumuler de dette technique.

Rollback instantané

Incident détecté

12:34 : Deploy new-payment-flow (10% users)
12:42 : Spike errors +500%
12:43 : Toggle flag OFF (via dashboard)
12:43:05 : Errors retour à la normale

MTTR : 5 secondes (vs 15-30min rollback traditionnel)

Automated rollback

// Monitoring auto-rollback
const metrics = await getMetrics('new-payment-flow');

if (metrics.errorRate > 0.5) {  // > 0.5%
  await featureFlags.disable('new-payment-flow');
  await alert.page(oncall, 'Auto-rollback triggered');
}

Patterns avancés

Pattern 1 : Rampe de montée automatique

// Rollout automatique si métriques OK
const rolloutSchedule = [
  { day: 1, percentage: 5 },
  { day: 3, percentage: 25 },
  { day: 5, percentage: 50 },
  { day: 7, percentage: 100 },
];

for (const stage of rolloutSchedule) {
  await wait(stage.day);

  const metrics = await getMetrics();
  if (metrics.errorRate < threshold) {
    await featureFlags.setRollout('feature', stage.percentage);
  } else {
    await featureFlags.disable('feature');
    break;
  }
}

Pattern 2 : Targeting géographique

// Rollout par région
featureFlags.configure('new-feature', {
  targeting: {
    custom: {
      country: { in: ['FR', 'BE', 'CH'] }  // DACH d'abord
    }
  }
});

// 1 semaine plus tard : élargir
country: { in: ['FR', 'BE', 'CH', 'DE', 'AT', 'NL'] }

Pattern 3 : Ring deployment

// Rings : Dev → Staging → Prod
const rings = [
  { env: 'dev', users: ['all'] },
  { env: 'staging', users: ['internal'] },
  { env: 'prod', users: ['beta'] },
  { env: 'prod', rollout: 10 },
  { env: 'prod', rollout: 100 },
];

Métriques et monitoring

Dashboard Feature Flags

┌─────────────────────────────────────┐
│ Feature: new-checkout-flow          │
│ Status: 🟡 Rolling out (25%)       │
├─────────────────────────────────────┤
│ Error rate: 0.12% (↓ vs baseline) │
│ Latency p95: 245ms (↑ +12ms)      │
│ Conversion: 3.2% (↑ +0.3%)        │
│                                     │
│ [Increase to 50%] [Rollback]       │
└─────────────────────────────────────┘

Alertes automatiques

# Prometheus alert
- alert: FeatureFlagHighErrorRate
  expr: |
    feature_flag_error_rate{flag="new-feature"} > 0.5
  for: 5m
  annotations:
    summary: "Feature flag {{ $labels.flag }} error rate high"
    action: "Consider rolling back via dashboard"

Analytics impact business

-- Mesurer impact conversion
SELECT
  CASE
    WHEN feature_flag = 'new-checkout'
    THEN 'new'
    ELSE 'old'
  END AS variant,
  COUNT(*) as sessions,
  SUM(converted) as conversions,
  SUM(converted) / COUNT(*) as conversion_rate
FROM sessions
WHERE date > NOW() - INTERVAL '7 days'
GROUP BY variant;

Erreurs à éviter

Erreur 1 : Trop de flags

// ❌ Flags qui durent 6+ mois
if (featureFlags.isEnabled('old-feature-from-2024')) {
  // Debt technique
}

Solution : Cleanup automatique après 30 jours.

Erreur 2 : Nested flags

// ❌ Flags imbriqués = cauchemar
if (featureFlags.isEnabled('feature-a')) {
  if (featureFlags.isEnabled('feature-b')) {
    if (featureFlags.isEnabled('feature-c')) {
      // Quelle combinaison est testée ???
    }
  }
}

Solution : 1 flag = 1 feature indépendante.

Erreur 3 : Pas de fallback

// ❌ Si service Feature Flags down = app crash
const isEnabled = await featureFlags.isEnabled('feature');

// ✅ Fallback si service down
const isEnabled = await featureFlags
  .isEnabled('feature')
  .catch(() => false);  // Safe default

Erreur 4 : Feature Flags en database

-- ❌ Query DB pour chaque check
SELECT enabled FROM feature_flags WHERE name = 'feature';

Solution : Cache en mémoire, refresh périodique.

Coûts et ROI

Solutions Feature Flags

LaunchDarkly (SaaS) :

  • $50-$100/utilisateur/mois
  • Toutes features
  • Support 24/7

Unleash (Open Source) :

  • Gratuit (self-hosted)
  • Infrastructure : $50-$200/mois
  • Maintenance : 0.2 ETP

Custom (fait maison) :

  • Dev : 2-3 semaines
  • Maintenance : 0.1 ETP

ROI mesuré

Avant Feature Flags :

  • Incidents/mois : 8
  • MTTR moyen : 25 minutes
  • Coût incidents : ~$50k/an

Après Feature Flags :

  • Incidents/mois : 2
  • MTTR moyen : 30 secondes
  • Économie : ~$35k/an

ROI : Payé en 3 mois (même avec LaunchDarkly).

Getting started

Semaine 1 : Setup

# Installer Unleash (Docker)
docker run -d -p 4242:4242 unleashorg/unleash-server

# Ou LaunchDarkly
npm install launchdarkly-js-client-sdk

Semaine 2 : Premier flag

// Feature simple, low-risk
if (featureFlags.isEnabled('new-footer')) {
  return <NewFooter />;
}
return <OldFooter />;

Semaine 3-4 : Rollout

Jour 1 : Admins
Jour 3 : Interne
Jour 5 : 5% users
Jour 7 : 25% users
Jour 10 : 100% users
Jour 15 : Cleanup flag

Semaine 5+ : Généraliser

Systématiser pour toutes nouvelles features.

Conclusion

Les Feature Flags transforment le déploiement :

Avant :

  • Deploy = stress
  • Rollback = 15-30min
  • Testing en prod = impossible

Après :

  • Deploy = routine
  • Rollback = 1 seconde
  • Testing en prod = safe

Investissement minimal, impact maximum.

Commencez petit :

  1. Setup Unleash (gratuit)
  2. Tester sur 1 feature low-risk
  3. Mesurer l’impact
  4. Généraliser

Et vous, déjà des Feature Flags en prod ?