# Feature Flags Dinámicos **Versión:** 1.0.0 **Origen:** projects/gamilit **Estado:** Producción **Última actualización:** 2025-12-08 --- ## Descripción Sistema de feature flags para activación gradual de funcionalidades: - Activar/desactivar features sin redespliegue - Rollout gradual por porcentaje de usuarios - Target por usuarios específicos o roles - Condiciones personalizadas via JSONB - Período de validez (starts_at/ends_at) - Multi-tenant opcional --- ## Características | Característica | Descripción | |----------------|-------------| | Toggle dinámico | On/off sin redespliegue | | Rollout gradual | 0-100% con hash consistente | | Target users | Early access para usuarios específicos | | Target roles | Habilitar por rol (admin, user, etc.) | | Condiciones | Reglas personalizadas vía JSONB | | Período | Fecha inicio/fin automático | | Multi-tenant | Flags globales o por tenant | | Auditoría | Tracking de quién creó/modificó | --- ## Stack Tecnológico ```yaml backend: framework: NestJS orm: TypeORM database: PostgreSQL algoritmos: - SHA256 hash para rollout consistente - Determinístico por userId + featureKey ``` --- ## Dependencias NPM ```json { "typeorm": "^0.3.x", "@nestjs/typeorm": "^10.x" } ``` **Nota:** No requiere librerías externas de feature flags (LaunchDarkly, Unleash, etc.) --- ## Tablas Requeridas | Tabla | Propósito | |-------|-----------| | system_configuration.feature_flags | Configuración de flags | --- ## Estructura del Módulo ``` feature-flags/ ├── entities/ │ └── feature-flag.entity.ts ├── services/ │ └── feature-flags.service.ts ├── controllers/ │ └── feature-flags.controller.ts ├── dto/ │ ├── create-feature-flag.dto.ts │ ├── update-feature-flag.dto.ts │ └── feature-flag-query.dto.ts ├── decorators/ │ └── feature-flag.decorator.ts # @FeatureFlag('key') └── guards/ └── feature-flag.guard.ts ``` --- ## Modelo de Datos ### FeatureFlag ```typescript interface FeatureFlag { id: string; // UUID tenant_id?: string; // null = global feature_key: string; // "new_checkout" (único) feature_name: string; // "Nuevo Checkout v2" description?: string; is_enabled: boolean; // Toggle principal rollout_percentage: number; // 0-100 target_users?: string[]; // UUIDs con early access target_roles?: string[]; // ['admin', 'beta_tester'] target_conditions: Record; // { min_level: 5 } starts_at?: Date; // Fecha inicio ends_at?: Date; // Fecha fin metadata: Record; // { category: 'checkout' } created_by?: string; updated_by?: string; created_at: Date; updated_at: Date; } ``` --- ## Flujo de Evaluación ``` isEnabled(key, userId, userRoles)? │ ▼ 1. Feature habilitada globalmente? └─► NO → return false │ ▼ 2. Usuario en target_users? └─► SÍ → return true (early access) │ ▼ 3. Usuario tiene target_role? └─► SÍ → return true │ ▼ 4. rollout_percentage == 100? └─► SÍ → return true │ ▼ 5. rollout_percentage == 0? └─► SÍ → return false │ ▼ 6. Hash(userId + key) < rollout_percentage? └─► SÍ → return true └─► NO → return false ``` --- ## Uso Rápido ### 1. Verificar feature programáticamente ```typescript import { FeatureFlagsService } from '@/modules/feature-flags/services'; // En un servicio const result = await featureFlagsService.isEnabled( 'new_checkout', // feature key userId, // opcional userRoles, // opcional: ['admin', 'user'] ); if (result.enabled) { // Usar nueva funcionalidad } else { // Usar funcionalidad legacy } ``` ### 2. Guard para proteger rutas ```typescript // Proteger endpoint completo @Get('beta-feature') @UseGuards(JwtAuthGuard, FeatureFlagGuard) @FeatureFlag('beta_feature') async betaFeature() { return { message: 'Solo para usuarios con feature habilitada' }; } ``` ### 3. Decorador para check inline ```typescript @Get('dashboard') async getDashboard( @CurrentUser() user: User, @CheckFeature('new_dashboard') hasNewDashboard: boolean, ) { if (hasNewDashboard) { return this.newDashboardService.getData(user.id); } return this.legacyDashboardService.getData(user.id); } ``` ### 4. En frontend (API call) ```typescript // GET /api/feature-flags/check/new_checkout const response = await api.get(`/feature-flags/check/${featureKey}`, { params: { userId }, }); if (response.data.enabled) { showNewCheckout(); } else { showLegacyCheckout(); } ``` --- ## Rollout Gradual El rollout usa un hash SHA256 del `userId + featureKey` para determinar si un usuario está en el porcentaje: ```typescript // Algoritmo de hash consistente private hashUserId(userId: string, featureKey: string): number { const hash = createHash('sha256') .update(`${userId}:${featureKey}`) .digest('hex'); const hashInt = parseInt(hash.substring(0, 8), 16); return hashInt % 101; // 0-100 } // Verificación const userHash = hashUserId(userId, featureKey); const isInRollout = userHash < rollout_percentage; ``` **Beneficios:** - Mismo usuario siempre obtiene mismo resultado - Distribución uniforme entre usuarios - Diferente distribución por feature (gracias al featureKey) --- ## Estrategias de Rollout ### Canary Release ```typescript // 1. Crear flag deshabilitada await featureFlagsService.create({ key: 'new_payment_gateway', name: 'Nuevo Gateway de Pagos', isEnabled: false, rolloutPercentage: 0, }); // 2. Habilitar para equipo interno await featureFlagsService.update('new_payment_gateway', { isEnabled: true, targetUsers: ['dev-team-uuid-1', 'dev-team-uuid-2'], }); // 3. Expandir a 5% de usuarios await featureFlagsService.updateRollout('new_payment_gateway', 5); // 4. Si todo OK, expandir gradualmente await featureFlagsService.updateRollout('new_payment_gateway', 25); await featureFlagsService.updateRollout('new_payment_gateway', 50); await featureFlagsService.updateRollout('new_payment_gateway', 100); ``` ### Beta por Roles ```typescript await featureFlagsService.create({ key: 'advanced_analytics', name: 'Analytics Avanzados', isEnabled: true, rolloutPercentage: 0, // No rollout general targetRoles: ['admin', 'premium_user'], }); ``` ### Feature Temporal ```typescript await featureFlagsService.create({ key: 'black_friday_banner', name: 'Banner Black Friday', isEnabled: true, rolloutPercentage: 100, startsAt: new Date('2025-11-25'), endsAt: new Date('2025-11-30'), }); ``` --- ## Variables de Entorno ```env # Feature flags FEATURE_FLAGS_CACHE_TTL=300 # Cache de 5 minutos FEATURE_FLAGS_DEFAULT_ENABLED=false ``` --- ## Endpoints Principales | Método | Ruta | Descripción | |--------|------|-------------| | GET | /admin/feature-flags | Listar todas las flags | | GET | /admin/feature-flags/:key | Obtener flag por key | | POST | /admin/feature-flags | Crear nueva flag | | PUT | /admin/feature-flags/:key | Actualizar flag | | DELETE | /admin/feature-flags/:key | Eliminar flag | | POST | /admin/feature-flags/:key/enable | Habilitar flag | | POST | /admin/feature-flags/:key/disable | Deshabilitar flag | | PUT | /admin/feature-flags/:key/rollout | Actualizar porcentaje | | POST | /feature-flags/:key/check | Verificar si está habilitada | --- ## Casos de Uso Comunes | Caso | Configuración | |------|---------------| | A/B Testing | Rollout 50%, metadata con grupo | | Beta cerrada | targetUsers con UUIDs específicos | | Kill switch | is_enabled = false cuando hay problemas | | Feature por plan | targetRoles: ['premium', 'enterprise'] | | Lanzamiento programado | starts_at + ends_at | | Migración gradual | Rollout 10% → 25% → 50% → 100% | --- ## Adaptaciones Necesarias 1. **Roles**: Ajustar enum de roles según tu sistema 2. **Cache**: Implementar cache si hay muchas verificaciones 3. **Multi-tenant**: Decidir si flags son globales o por tenant 4. **UI Admin**: Crear interfaz para gestionar flags 5. **Métricas**: Integrar con sistema de analytics --- ## Referencias - [Feature Flags Best Practices](https://martinfowler.com/articles/feature-toggles.html) - [Gradual Rollout Strategies](https://docs.launchdarkly.com/home/targeting-flags) --- **Mantenido por:** Sistema NEXUS **Proyecto origen:** Gamilit Platform