Capítulo 7: Llevando tus Workflows a Producción
Después de construir workflows complejos, sistemas de streaming e integraciones externas, es momento de hablar sobre cómo llevar todo esto a producción. En este capítulo final aprenderemos los patrones esenciales, mejores prácticas y técnicas que necesitas para que tus workflows funcionen de manera confiable en el mundo real.
La Taquería Doña Carmen ha crecido. Ya no es solo una taquería, sino una cadena con múltiples sucursales. Ahora necesitamos pensar en:
- Confiabilidad: ¿Qué pasa si algo falla?
- Escalabilidad: ¿Cómo manejar más pedidos?
- Monitoreo: ¿Cómo saber si todo funciona bien?
- Costos: ¿Cómo optimizar el uso de APIs?
- Mantenimiento: ¿Cómo encontrar y arreglar problemas?
No necesitas código complejo para esto. Necesitas principios claros y patrones simples.
Los 5 pilares de workflows en producción
1. Manejo de errores inteligente
En producción, todo puede fallar. Tu workflow debe estar preparado:
1// ❌ Malo: Sin manejo de errores 2const resultado = await apiExterna.procesar(datos); 3 4// ✅ Bueno: Con manejo robusto 5try { 6 const resultado = await apiExterna.procesar(datos); 7 return resultado; 8} catch (error) { 9 // Intentar con servicio alternativo 10 return await servicioAlternativo.procesar(datos); 11} 12
Principios clave:
- Fail fast: Detecta problemas rápidamente
- Fail safe: Siempre ten un plan B
- Fail gracefully: Degrada funcionalidad, no rompas todo
2. Caché inteligente
No hagas la misma consulta dos veces:
1// Caché simple pero efectivo 2const cache = new Map(); 3 4const obtenerDatos = async (clave) => { 5 // Verificar caché primero 6 if (cache.has(clave)) { 7 return cache.get(clave); 8 } 9 10 // Si no está, consultar y guardar 11 const datos = await api.consultar(clave); 12 cache.set(clave, datos); 13 14 // Limpiar después de 5 minutos 15 setTimeout(() => cache.delete(clave), 300000); 16 17 return datos; 18}; 19
Cuándo usar caché:
- Datos que no cambian frecuentemente
- Consultas costosas (tiempo o dinero)
- Información que se reutiliza mucho
3. Rate limiting y protección
Protege tus APIs y las de terceros:
1// Rate limiter simple 2class RateLimiter { 3 private requests = new Map(); 4 5 isAllowed(userId: string, limit = 100, windowMs = 60000) { 6 const now = Date.now(); 7 const userRequests = this.requests.get(userId) || []; 8 9 // Filtrar requests dentro de la ventana 10 const validRequests = userRequests.filter((time) => now - time < windowMs); 11 12 if (validRequests.length < limit) { 13 validRequests.push(now); 14 this.requests.set(userId, validRequests); 15 return true; 16 } 17 18 return false; 19 } 20} 21
Por qué es importante:
- Evita que un usuario abuse del sistema
- Protege tus costos de APIs externas
- Mantiene el servicio disponible para todos
4. Monitoreo y observabilidad
Necesitas saber qué está pasando:
1// Logger simple pero útil 2const logger = { 3 info: (message, data = {}) => { 4 console.log(`[INFO] ${message}`, { 5 timestamp: new Date().toISOString(), 6 ...data, 7 }); 8 }, 9 10 error: (message, error, data = {}) => { 11 console.error(`[ERROR] ${message}`, { 12 timestamp: new Date().toISOString(), 13 error: error.message, 14 stack: error.stack, 15 ...data, 16 }); 17 }, 18}; 19 20// Usar en tus workflows 21const procesarPedido = async (pedido) => { 22 logger.info("Iniciando procesamiento de pedido", { pedidoId: pedido.id }); 23 24 try { 25 const resultado = await workflow.procesar(pedido); 26 logger.info("Pedido procesado exitosamente", { 27 pedidoId: pedido.id, 28 tiempo: resultado.tiempo, 29 }); 30 return resultado; 31 } catch (error) { 32 logger.error("Error procesando pedido", error, { pedidoId: pedido.id }); 33 throw error; 34 } 35}; 36
Qué monitorear:
- Tiempo de respuesta de cada operación
- Errores y su frecuencia
- Uso de recursos (memoria, CPU)
- Métricas de negocio (pedidos por hora, ingresos)
5. Configuración y secretos
Nunca hardcodees credenciales:
1// ❌ Malo: Credenciales en el código 2const apiKey = "sk_live_abc123..."; 3 4// ✅ Bueno: Variables de entorno 5const apiKey = process.env.STRIPE_API_KEY; 6if (!apiKey) { 7 throw new Error("STRIPE_API_KEY no configurada"); 8} 9
Mejores prácticas:
- Usa variables de entorno para configuración
- Diferentes configuraciones para desarrollo/producción
- Nunca commits credenciales al repositorio
- Rota credenciales regularmente
Patrones de arquitectura probados
Patrón Circuit Breaker
Evita que un servicio caído tumbe todo tu sistema:
1class CircuitBreaker { 2 private failures = 0; 3 private lastFailTime = 0; 4 private state = "closed"; // closed, open, half-open 5 6 async call(fn, fallback) { 7 // Si está abierto, usar fallback 8 if (this.state === "open") { 9 if (Date.now() - this.lastFailTime > 60000) { 10 // 1 minuto 11 this.state = "half-open"; 12 } else { 13 return fallback(); 14 } 15 } 16 17 try { 18 const result = await fn(); 19 this.reset(); 20 return result; 21 } catch (error) { 22 this.recordFailure(); 23 return fallback(); 24 } 25 } 26 27 private recordFailure() { 28 this.failures++; 29 this.lastFailTime = Date.now(); 30 if (this.failures >= 5) { 31 this.state = "open"; 32 } 33 } 34 35 private reset() { 36 this.failures = 0; 37 this.state = "closed"; 38 } 39} 40
Patrón Retry con Backoff
Reintenta operaciones que fallan, pero inteligentemente:
1const retryWithBackoff = async (fn, maxRetries = 3) => { 2 for (let attempt = 1; attempt <= maxRetries; attempt++) { 3 try { 4 return await fn(); 5 } catch (error) { 6 if (attempt === maxRetries) { 7 throw error; 8 } 9 10 // Esperar más tiempo en cada intento: 1s, 2s, 4s 11 const delay = Math.pow(2, attempt - 1) * 1000; 12 await new Promise((resolve) => setTimeout(resolve, delay)); 13 } 14 } 15}; 16 17// Uso 18const resultado = await retryWithBackoff(() => api.procesarPago(datos), 3); 19
Patrón Queue para alta carga
Cuando tienes muchos pedidos simultáneos:
1class JobQueue { 2 private queue = []; 3 private processing = false; 4 5 async add(job) { 6 this.queue.push(job); 7 if (!this.processing) { 8 this.process(); 9 } 10 } 11 12 private async process() { 13 this.processing = true; 14 15 while (this.queue.length > 0) { 16 const job = this.queue.shift(); 17 try { 18 await job(); 19 } catch (error) { 20 console.error("Error procesando job:", error); 21 } 22 } 23 24 this.processing = false; 25 } 26} 27 28// Uso 29const queue = new JobQueue(); 30queue.add(() => procesarPedido(pedido1)); 31queue.add(() => procesarPedido(pedido2)); 32
Optimización de costos
Reduce llamadas a APIs costosas
1// Agrupa múltiples consultas 2const batchProcessor = { 3 pending: [], 4 timer: null, 5 6 add(item) { 7 this.pending.push(item); 8 9 if (!this.timer) { 10 this.timer = setTimeout(() => { 11 this.processBatch(); 12 }, 100); // Esperar 100ms para agrupar más 13 } 14 }, 15 16 async processBatch() { 17 const items = [...this.pending]; 18 this.pending = []; 19 this.timer = null; 20 21 // Procesar todos juntos 22 await api.processBatch(items); 23 }, 24}; 25
Usa caché para datos estáticos
1// Cachear menú por 1 hora 2const menuCache = { 3 data: null, 4 timestamp: 0, 5 6 async get() { 7 const now = Date.now(); 8 const oneHour = 60 * 60 * 1000; 9 10 if (!this.data || now - this.timestamp > oneHour) { 11 this.data = await api.getMenu(); 12 this.timestamp = now; 13 } 14 15 return this.data; 16 }, 17}; 18
Debugging en producción
Logs estructurados
1const createLogger = (service) => ({ 2 log: (level, message, data = {}) => { 3 const logEntry = { 4 timestamp: new Date().toISOString(), 5 service, 6 level, 7 message, 8 ...data, 9 }; 10 11 console.log(JSON.stringify(logEntry)); 12 }, 13}); 14 15const logger = createLogger("taqueria-workflow"); 16logger.log("info", "Pedido procesado", { 17 pedidoId: "123", 18 tiempo: "2.3s", 19 usuario: "maria@email.com", 20}); 21
Métricas simples
1const metrics = { 2 counters: new Map(), 3 timers: new Map(), 4 5 increment(name, value = 1) { 6 const current = this.counters.get(name) || 0; 7 this.counters.set(name, current + value); 8 }, 9 10 time(name, fn) { 11 const start = Date.now(); 12 const result = fn(); 13 const duration = Date.now() - start; 14 15 const times = this.timers.get(name) || []; 16 times.push(duration); 17 this.timers.set(name, times); 18 19 return result; 20 }, 21 22 getStats() { 23 return { 24 counters: Object.fromEntries(this.counters), 25 averageTimes: Object.fromEntries( 26 Array.from(this.timers.entries()).map(([name, times]) => [ 27 name, 28 times.reduce((a, b) => a + b, 0) / times.length, 29 ]) 30 ), 31 }; 32 }, 33}; 34 35// Uso 36metrics.increment("pedidos_procesados"); 37metrics.time("tiempo_procesamiento", () => procesarPedido(pedido)); 38
Checklist de producción
Antes de lanzar
- [ ] Manejo de errores en todas las operaciones críticas
- [ ] Rate limiting implementado
- [ ] Caché para datos frecuentes
- [ ] Logs estructurados configurados
- [ ] Variables de entorno para toda la configuración
- [ ] Timeouts apropiados en todas las llamadas externas
- [ ] Fallbacks para servicios críticos
- [ ] Monitoreo de métricas básicas
Después del lanzamiento
- [ ] Alertas configuradas para errores críticos
- [ ] Dashboard para monitorear métricas
- [ ] Backup de datos importantes
- [ ] Plan de rollback documentado
- [ ] Documentación actualizada
- [ ] Tests de carga realizados
Herramientas recomendadas
Para desarrollo local
- Docker para entornos consistentes
- Redis para caché y queues
- PostgreSQL para datos persistentes
Para producción
- PM2 para gestión de procesos Node.js
- Nginx como reverse proxy
- Let's Encrypt para SSL gratuito
Para monitoreo
- Grafana para dashboards
- Prometheus para métricas
- Sentry para tracking de errores
Casos de uso reales
E-commerce con alta demanda
1// Manejar picos de tráfico 2const orderProcessor = { 3 async processOrder(order) { 4 // 1. Validar disponibilidad (con caché) 5 const available = await this.checkInventory(order.items); 6 if (!available) { 7 throw new Error("Producto no disponible"); 8 } 9 10 // 2. Procesar pago (con retry) 11 const payment = await retryWithBackoff(() => 12 paymentService.charge(order.total) 13 ); 14 15 // 3. Actualizar inventario (en queue) 16 await inventoryQueue.add(() => inventory.update(order.items)); 17 18 // 4. Enviar confirmación (async) 19 emailQueue.add(() => sendConfirmation(order.email, order)); 20 21 return { orderId: order.id, status: "confirmed" }; 22 }, 23}; 24
Sistema de notificaciones
1// Enviar notificaciones sin bloquear 2const notificationService = { 3 async send(userId, message) { 4 // Intentar push notification primero 5 try { 6 await pushService.send(userId, message); 7 metrics.increment("notifications.push.success"); 8 } catch (error) { 9 // Fallback a email 10 await emailService.send(userId, message); 11 metrics.increment("notifications.email.fallback"); 12 } 13 }, 14}; 15
El futuro de tus workflows
Próximos pasos
- Implementa gradualmente - No cambies todo de una vez
- Mide todo - Si no lo mides, no lo puedes mejorar
- Automatiza - Deploy, tests, monitoreo
- Documenta - Tu yo del futuro te lo agradecerá
- Itera - Mejora basándote en datos reales
Tecnologías emergentes
- Edge computing para menor latencia
- Serverless para escalabilidad automática
- GraphQL para APIs más eficientes
- WebAssembly para performance crítica
Mantente actualizado
- Sigue la documentación oficial de LlamaIndex
- Únete a comunidades de desarrolladores
- Experimenta con nuevas funcionalidades
- Comparte tus aprendizajes
¡Felicidades! 🎉
Has completado el viaje desde workflows básicos hasta sistemas listos para producción. Ahora tienes las herramientas para:
- Construir sistemas confiables que manejen errores gracefully
- Optimizar performance con caché y rate limiting
- Monitorear efectivamente tus workflows en producción
- Debuggear problemas rápidamente cuando surjan
- Escalar tu sistema conforme crezca
Tu misión ahora
Toma estos patrones y construye algo increíble. Recuerda:
- Empieza simple - Puedes optimizar después
- Mide primero - Optimiza basándote en datos
- Falla rápido - Aprende de los errores
- Documenta todo - Tu equipo te lo agradecerá
¡Ahora ve y construye workflows que cambien el mundo! 🚀
Recursos para continuar
Documentación esencial
Comunidades
Libros recomendados
- "The Pragmatic Programmer" - Consejos atemporales
- "Clean Code" - Código que otros pueden entender
- "Site Reliability Engineering" - Sistemas que no fallan
¡Gracias por acompañarme en este viaje! 🌮✨
