Files
amayo/README/FEATURE_FLAGS_SYSTEM.md
Shni 89d475ba66 feat: Implement feature flag system with helpers, service, and loader
- Added feature flag helpers and decorators for easy usage in commands.
- Created a feature flag service for managing flags, including initialization, caching, and evaluation strategies.
- Implemented a loader to initialize the feature flag service on bot startup.
- Defined types for feature flags, including configurations, contexts, evaluations, and statistics.
- Provided examples of feature flag usage in commands, demonstrating various patterns such as A/B testing, gradual rollouts, and access control.
2025-10-31 21:12:27 -05:00

627 lines
13 KiB
Markdown

# 🎮 Feature Flags System
Sistema completo de Feature Flags para control de funcionalidades, rollouts progresivos, A/B testing y toggles dinámicos.
## 📋 Índice
- [Instalación](#instalación)
- [Conceptos](#conceptos)
- [Uso Básico](#uso-básico)
- [Ejemplos Avanzados](#ejemplos-avanzados)
- [Comando de Administración](#comando-de-administración)
- [Estrategias de Rollout](#estrategias-de-rollout)
- [Best Practices](#best-practices)
---
## 🚀 Instalación
### 1. Migración de Base de Datos
```bash
npx prisma migrate dev --name add_feature_flags
```
### 2. Inicialización del Servicio
En tu `src/loaders/` o punto de entrada principal:
```typescript
import { featureFlagService } from '@/core/services/FeatureFlagService';
// Inicializar el servicio
await featureFlagService.initialize();
```
---
## 🧠 Conceptos
### Estados de Flags
- **`enabled`**: Habilitado para todos
- **`disabled`**: Deshabilitado para todos
- **`rollout`**: Rollout progresivo según estrategia
- **`maintenance`**: Deshabilitado por mantenimiento
### Targets
- **`global`**: Aplica a todo el bot
- **`guild`**: Aplica por servidor
- **`user`**: Aplica por usuario
- **`channel`**: Aplica por canal
### Estrategias de Rollout
- **`percentage`**: Basado en % de usuarios
- **`whitelist`**: Solo IDs específicos
- **`blacklist`**: Todos excepto IDs específicos
- **`gradual`**: Rollout gradual en el tiempo
- **`random`**: Aleatorio por sesión
---
## 💡 Uso Básico
### 1. En Comandos con Decorador
```typescript
import { RequireFeature } from '@/core/lib/featureFlagHelpers';
class ShopCommand {
@RequireFeature('new_shop_system', {
fallbackMessage: '🔧 El nuevo sistema de tienda estará disponible pronto'
})
async execute(interaction: CommandInteraction) {
// Este código solo se ejecuta si el flag está habilitado
await interaction.reply('Bienvenido a la nueva tienda!');
}
}
```
### 2. Con Guard en el Handler
```typescript
import { featureGuard } from '@/core/lib/featureFlagHelpers';
async function handleMineCommand(interaction: CommandInteraction) {
// Check del flag
if (!await featureGuard('new_mining_system', interaction)) {
return; // Automáticamente responde al usuario
}
// Código del comando nuevo
await doNewMining(interaction);
}
```
### 3. Check Manual
```typescript
import { isFeatureEnabledForInteraction } from '@/core/lib/featureFlagHelpers';
async function execute(interaction: CommandInteraction) {
const useNewAlgorithm = await isFeatureEnabledForInteraction(
'improved_algorithm',
interaction
);
if (useNewAlgorithm) {
await newAlgorithm();
} else {
await oldAlgorithm();
}
}
```
---
## 🎯 Ejemplos Avanzados
### A/B Testing
```typescript
import { abTest, extractContext } from '@/core/lib/featureFlagHelpers';
async function handleShop(interaction: CommandInteraction) {
const context = extractContext(interaction);
const result = await abTest('new_shop_ui', context, {
variant: async () => {
// Nueva UI
return buildNewShopUI();
},
control: async () => {
// UI antigua
return buildOldShopUI();
}
});
await interaction.reply(result);
}
```
### Múltiples Flags (AND)
```typescript
import { requireAllFeatures, extractContext } from '@/core/lib/featureFlagHelpers';
async function handlePremiumFeature(interaction: CommandInteraction) {
const context = extractContext(interaction);
const hasAccess = await requireAllFeatures(
['premium_features', 'beta_access', 'new_ui'],
context
);
if (!hasAccess) {
await interaction.reply('No tienes acceso a esta funcionalidad');
return;
}
// Código de la feature premium
}
```
### Múltiples Flags (OR)
```typescript
import { requireAnyFeature, extractContext } from '@/core/lib/featureFlagHelpers';
async function handleSpecialEvent(interaction: CommandInteraction) {
const context = extractContext(interaction);
const hasEventAccess = await requireAnyFeature(
['halloween_event', 'christmas_event', 'beta_events'],
context
);
if (!hasEventAccess) {
await interaction.reply('No hay eventos activos para ti');
return;
}
// Código del evento
}
```
### Con Fallback
```typescript
import { withFeature, extractContext } from '@/core/lib/featureFlagHelpers';
async function getData(interaction: CommandInteraction) {
const context = extractContext(interaction);
const data = await withFeature(
'new_data_source',
context,
async () => {
// Fuente nueva
return fetchFromNewAPI();
},
async () => {
// Fuente antigua (fallback)
return fetchFromOldAPI();
}
);
return data;
}
```
---
## 🛠️ Comando de Administración
### Crear un Flag
```
/featureflags create name:new_shop_system status:disabled target:global description:"Nuevo sistema de tienda"
```
### Listar Flags
```
/featureflags list
```
### Ver Info de un Flag
```
/featureflags info flag:new_shop_system
```
### Actualizar Estado
```
/featureflags update flag:new_shop_system status:enabled
```
### Configurar Rollout Progresivo
```
/featureflags rollout flag:new_shop_system strategy:percentage percentage:25
```
Esto habilitará la feature para el 25% de los usuarios.
### Configurar Rollout Gradual
```typescript
// Programáticamente
await featureFlagService.setFlag({
name: 'new_combat_system',
status: 'rollout',
target: 'user',
rolloutStrategy: 'gradual',
rolloutConfig: {
gradual: {
startPercentage: 10, // Empieza con 10%
targetPercentage: 100, // Llega al 100%
durationDays: 7 // En 7 días
}
},
startDate: new Date()
});
```
### Ver Estadísticas
```
/featureflags stats flag:new_shop_system
```
### Refrescar Caché
```
/featureflags refresh
```
### Eliminar Flag
```
/featureflags delete flag:old_feature
```
---
## 📊 Estrategias de Rollout
### 1. Percentage (Porcentaje)
Distribuye la feature a un % de usuarios de forma determinista.
```typescript
await featureFlagService.setFlag({
name: 'feature_x',
status: 'rollout',
target: 'user',
rolloutStrategy: 'percentage',
rolloutConfig: {
percentage: 50 // 50% de usuarios
}
});
```
### 2. Whitelist (Lista Blanca)
Solo para IDs específicos.
```typescript
await featureFlagService.setFlag({
name: 'beta_features',
status: 'rollout',
target: 'user',
rolloutStrategy: 'whitelist',
rolloutConfig: {
targetIds: [
'123456789', // User ID 1
'987654321' // User ID 2
]
}
});
```
### 3. Blacklist (Lista Negra)
Para todos excepto IDs específicos.
```typescript
await featureFlagService.setFlag({
name: 'stable_feature',
status: 'rollout',
target: 'guild',
rolloutStrategy: 'blacklist',
rolloutConfig: {
targetIds: [
'guild_id_problematico'
]
}
});
```
### 4. Gradual (Progresivo en el Tiempo)
Rollout gradual durante X días.
```typescript
await featureFlagService.setFlag({
name: 'major_update',
status: 'rollout',
target: 'user',
rolloutStrategy: 'gradual',
rolloutConfig: {
gradual: {
startPercentage: 5, // Empieza con 5%
targetPercentage: 100, // Termina en 100%
durationDays: 14 // Durante 14 días
}
},
startDate: new Date() // Importante: define cuándo empieza
});
```
---
## ✨ Best Practices
### 1. Nombres Claros y Descriptivos
```typescript
// ❌ Mal
'flag_1'
'test'
'new'
// ✅ Bien
'new_shop_ui_v2'
'improved_combat_algorithm'
'halloween_2025_event'
```
### 2. Siempre con Descripción
```typescript
await featureFlagService.setFlag({
name: 'new_mining_system',
description: 'Sistema de minería rediseñado con durabilidad de herramientas',
status: 'disabled',
target: 'global'
});
```
### 3. Rollouts Graduales para Cambios Grandes
Para cambios importantes, usa rollout gradual:
1. Día 1-3: 10% de usuarios
2. Día 4-7: 50% de usuarios
3. Día 8-14: 100% de usuarios
### 4. Limpiar Flags Obsoletos
Una vez que una feature está 100% desplegada y estable:
1. Elimina el flag
2. Elimina el código del check
3. Mantén solo la nueva implementación
### 5. Usar Whitelists para Beta Testers
```typescript
await featureFlagService.setFlag({
name: 'experimental_features',
status: 'rollout',
target: 'user',
rolloutStrategy: 'whitelist',
rolloutConfig: {
targetIds: BETA_TESTER_IDS // Array de tus beta testers
}
});
```
### 6. Fechas de Expiración para Eventos
```typescript
await featureFlagService.setFlag({
name: 'christmas_2025_event',
status: 'enabled',
target: 'global',
startDate: new Date('2025-12-01'),
endDate: new Date('2025-12-31')
});
```
El flag se auto-deshabilitará después del 31 de diciembre.
### 7. Caché y Performance
El servicio cachea flags en memoria por 5 minutos. Si necesitas actualizaciones inmediatas:
```
/featureflags refresh
```
O programáticamente:
```typescript
await featureFlagService.refreshCache();
```
---
## 🔥 Casos de Uso Reales
### Lanzamiento de Comando Nuevo
```typescript
// Fase 1: Desarrollo - Deshabilitado
await featureFlagService.setFlag({
name: 'pvp_arena_command',
status: 'disabled',
target: 'global'
});
// Fase 2: Beta Testing - Solo whitelisted
await featureFlagService.setFlag({
name: 'pvp_arena_command',
status: 'rollout',
target: 'guild',
rolloutStrategy: 'whitelist',
rolloutConfig: {
targetIds: ['guild_beta_1', 'guild_beta_2']
}
});
// Fase 3: Rollout Progresivo - 25%
await featureFlagService.setFlag({
name: 'pvp_arena_command',
status: 'rollout',
target: 'user',
rolloutStrategy: 'percentage',
rolloutConfig: { percentage: 25 }
});
// Fase 4: Habilitado para Todos
await featureFlagService.setFlag({
name: 'pvp_arena_command',
status: 'enabled',
target: 'global'
});
// Fase 5: Cleanup - Eliminar flag y código del check
await featureFlagService.removeFlag('pvp_arena_command');
```
### Migración de Sistema Antiguo a Nuevo
```typescript
// En el comando
async function handleInventory(interaction: CommandInteraction) {
const context = extractContext(interaction);
await abTest('inventory_system_v2', context, {
variant: async () => {
// Sistema nuevo
return await newInventorySystem.show(interaction);
},
control: async () => {
// Sistema antiguo
return await oldInventorySystem.show(interaction);
}
});
}
```
Luego gradualmente aumentas el % hasta 100% y eliminas el código antiguo.
### Kill Switch para Emergencias
```typescript
// Si hay un bug crítico en una feature:
await featureFlagService.setFlag({
name: 'problematic_feature',
status: 'maintenance', // Deshabilitado inmediatamente
target: 'global'
});
// O via comando Discord:
// /featureflags update flag:problematic_feature status:maintenance
```
---
## 📚 API Reference
Ver `src/core/types/featureFlags.ts` para tipos completos.
### FeatureFlagService
```typescript
// Inicializar
await featureFlagService.initialize();
// Check si está habilitado
const enabled = await featureFlagService.isEnabled('flag_name', context);
// Crear/actualizar flag
await featureFlagService.setFlag(config);
// Eliminar flag
await featureFlagService.removeFlag('flag_name');
// Obtener flag
const flag = featureFlagService.getFlag('flag_name');
// Obtener todos los flags
const flags = featureFlagService.getFlags();
// Estadísticas
const stats = featureFlagService.getStats('flag_name');
const allStats = featureFlagService.getAllStats();
// Refrescar caché
await featureFlagService.refreshCache();
featureFlagService.clearEvaluationCache();
```
### Helpers
```typescript
// Check básico
await isFeatureEnabled(flagName, context);
await isFeatureEnabledForInteraction(flagName, interaction);
// Guards
await featureGuard(flagName, interaction, options);
// Decorador
@RequireFeature('flag_name', options)
// A/B Testing
await abTest(flagName, context, { variant, control });
// Wrapper
await withFeature(flagName, context, fn, fallback);
// Múltiples flags
await requireAllFeatures(flags, context); // AND
await requireAnyFeature(flags, context); // OR
```
---
## 🎮 Integración con tu Bot
El sistema se integra automáticamente si añades la inicialización en tu loader:
```typescript
// src/loaders/featureFlagsLoader.ts
import { featureFlagService } from '../services/FeatureFlagService';
import logger from '../lib/logger';
export async function loadFeatureFlags() {
try {
await featureFlagService.initialize();
logger.info('[FeatureFlags] Sistema inicializado');
} catch (error) {
logger.error('[FeatureFlags] Error al inicializar:', error);
}
}
```
Luego en tu `main.ts` o donde cargues servicios:
```typescript
import { loadFeatureFlags } from './loaders/featureFlagsLoader';
// ...
await loadFeatureFlags();
// ...
```
---
Creado con 🎮 para el bot Amayo