De Monolito a Microservicios — Estrategias de Migración
Guía práctica para descomponer monolitos: strangler fig, branch by abstraction y patrones de extracción incremental que reducen riesgo y preservan continuidad del negocio.
Introducción
Migrar de un monolito a microservicios es uno de los proyectos de refactorización más riesgosos. Hecho mal, crea un monolito distribuido — más lento y complejo. Hecho bien, habilita autonomía de equipo y delivery más rápido. A continuación: estrategias de descomposición segura.
Antes de Empezar
Valida la Decisión
| Pregunta | Si “no”, reconsidera |
|---|---|
| ¿Tienes > 50 ingenieros? | Monolito + límites modulares puede bastar |
| ¿Los deploys son dolorosos? | Arregla CI/CD primero |
| ¿Puedes medir el costo del monolito? | Cuantifica el dolor antes de justificar |
| ¿Tienes expertise operativo? | Los microservicios necesitan madurez DevOps/SRE |
Identifica el Primer Servicio a Extraer
Elige un servicio que sea: bajo riesgo, alto dolor, límite claro, valor independiente. Buenos candidatos: notificaciones, reporting, o feature flags.
Patrones de Migración
1. Strangler Fig (Higuera Estranguladora)
Reemplaza gradualmente funcionalidad del monolito redirigiendo tráfico a nuevos servicios.
Fase 1: [Usuarios] → [Monolito]
Fase 2: [Usuarios] → [API Gateway] → [Monolito] + [Nuevo Servicio]
Fase 3: [Usuarios] → [API Gateway] → [Servicios]
class Router:
def route(self, request):
if flags.is_enabled("use-new-catalog", request.user_id):
return self.catalog_service.handle(request)
return self.monolith.handle(request)
Por qué funciona: Rollback instantáneo via feature flag. El monolito es tu red de seguridad.
2. Branch by Abstraction
Crea una abstracción en el monolito, luego intercambia la implementación.
class NotificationSender(ABC):
@abstractmethod
def send(self, user, message): pass
class MonolithNotificationSender(NotificationSender): ...
class ServiceNotificationSender(NotificationSender): ...
sender = ServiceNotificationSender() if flags.enabled("new") else MonolithNotificationSender()
Por qué funciona: El monolito nunca sabe que habla con un servicio. Rollback de una línea.
3. Parallel Run
Corre ambas implementaciones, compara outputs, pero solo sirve el viejo.
old = monolith_recommendations.get(user_id)
new = recommendations_service.get(user_id)
if old != new:
logger.warning("Divergencia", old=old, new=new)
return old
Cuándo usar: Correctness crítico (pagos, precios, recomendaciones).
4. Migración de Datos
| Patrón | Uso |
|---|---|
| BD compartida (temporal) | Semanas, no meses |
| Change Data Capture (Debezium) | Sincronizar sin modificar el monolito |
| Dual-write y switch | Escribir a ambas bases, luego cambiar lecturas |
Hoja de Ruta
| Trimestre | Objetivo |
|---|---|
| Q1 | Extraer servicio no crítico con Strangler Fig |
| Q2 | Implementar API Gateway y service discovery |
| Q3 | Extraer servicio medio-crítico con Branch by Abstraction |
| Q4 | Parallel run para un servicio alto-crítico |
| Año 2 | Extraer servicios core; monolito se encoge |
| Año 3 | Retirar monolito |
Lo que funciona
- Nunca hagas un rewrite big-bang — la extracción incremental preserva opcionalidad
- Mide antes y después: deploy frequency, lead time, failure rate
- Mantén el monolito deployable — no dejes que la extracción rompa CI/CD
- Invierte en testing — contract tests capturan breaking changes
- Acepta que algún código nunca se mueve
Errores Comunes
- Extraer servicios basados en capas técnicas en vez de capacidades de negocio
- Ignorar consistencia de datos — las transacciones distribuidas requieren sagas
- Subestimar el “último 10%” — los servicios finales son los más acoplados
- Remover el monolito demasiado temprano — es tu red de seguridad
- No invertir en experiencia de desarrollador — local dev y testing son más difíciles con microservicios
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de architecture y devops para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica de monolito a microservicios — estrategias de migración cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Escenario Detallado: Extraccion del Servicio de Notificaciones
Sistema: E-commerce monolito (Rails, 450k lineas, 30 devs)
Servicio a extraer: Notificaciones (email + SMS + push)
Riesgo: Bajo (no afecta pagos ni pedidos)
Esfuerzo estimado: 6 semanas
Semana 1-2: Branch by Abstraction
1. Crear interfaz NotificationSender en el monolito
- send_email(user_id, template, data)
- send_sms(user_id, message)
- send_push(user_id, payload)
2. Mover implementacion existente a MonolithNotificationSender
3. Todos los callers usan la interfaz via DI
4. Tests existentes siguen pasando sin cambios
Semana 3-4: Construir el servicio
$ mkdir notification-service && cd notification-service
$ npm init -y && npm install fastify aws-sdk pino
$ mkdir src && cat > src/handler.js << EOF
async function sendEmail(req, reply) {
const { user_id, template, data } = req.body;
const result = await ses.sendTemplatedEmail({
Source: "no-reply@app.com",
Destination: { ToAddresses: [await getUserEmail(user_id)] },
Template: template,
TemplateData: JSON.stringify(data)
}).promise();
return { id: result.MessageId, status: "sent" };
}
EOF
$ docker build -t notification-service:latest .
$ kubectl apply -f k8s/deployment.yaml
Semana 5: Parallel run
- Feature flag: use_new_notification_service (50% de usuarios)
- Loggear divergencias entre monolito y servicio
- Monitorear latencia y tasa de error
- Configuracion del feature flag:
flags.configure("use_new_notification_service", {
percentage: 50,
user_id_hash: true // consistencia: mismo user siempre va al mismo destino
})
Semana 6: Cutover
- Subir a 100% en ventana de bajo trafico (domingo 2am)
- Monitorear 24h con rollback listo
- Despues de 7 dias estables, remover MonolithNotificationSender
- Despues de 30 dias, remover codigo de notificaciones del monolito
Metricas antes/despues:
| Metrica | Monolito | Servicio |
|---------|----------|----------|
| Deploy frequency | Semanal | Diario |
| Lead time | 3 dias | 2 horas |
| P95 latencia send_email | 800ms | 120ms |
| Tasa de error | 0.8% | 0.1% |
Como manejo la base de datos compartida durante la migracion?
El patron mas seguro es Change Data Capture (CDC). Usa Debezium para replicar cambios del monolito al servicio nuevo sin modificar el codigo del monolito. El servicio nuevo lee de su propia base de datos alimentada por CDC. Cuando confias en la replicacion, cambia las escrituras al servicio nuevo. Finalmente, elimina la tabla del monolito. Nunca dejes dos servicios escribiendo a la misma tabla: las condiciones de carrera son inevitables.
Que hago cuando el equipo se resiste a la migracion?
La resistencia suele ser racional: los devs conocen el monolito y temen lo desconocido. Empieza con un servicio de bajo riesgo y documenta el proceso en detalle. Invita al equipo escptico a participar en la primera extraccion. Muestra las metricas despues del cutover: deploy frequency, lead time, tasa de error. Los datos convencen mas que las presentaciones. Si la resistencia persiste, reconsidera si la migracion es necesaria.
Como testeo durante la migracion?
Usa contract tests entre el monolito y el servicio nuevo. El monolito es consumidor del servicio durante la transicion. Pact tests verifican que el contrato no se rompe. Parallel run es tu test de integracion mas valioso: si ambas implementaciones producen el mismo resultado para los mismos inputs, la migracion es segura. Agrega tests E2E que cubran el flujo completo a traves del nuevo servicio.
End of document. Review and update quarterly.
Troubleshooting
- High latency between services: trace the request path. Look for synchronous chains, missing caching, and oversized payloads that cross network boundaries.
- Single point of failure: identify components without redundancy. Add replicas, failover, or circuit breakers before scaling traffic.
- Unexpected coupling between services: review shared databases, libraries, and schemas. Bound contexts should own their data and expose stable interfaces.
- Cost spikes after scaling: Reserved capacity or spot instances can reduce steady-state spend.
- Difficult to reason about the system: maintain architecture decision records and service dependency maps.
Errores Comunes en Producción
- Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
- Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
- Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
- No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
- Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
- No asignar un responsable que revise decisiones trimestralmente.
- Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
- Olvidar medir resultados antes de agregar la siguiente mejora.
Preguntas frecuentes
¿Cuánto dura una migración de monolito a microservicios?
Para un sistema con 100+ ingenieros, espera 1-3 años. El primer servicio toma meses; el décimo toma semanas. El cuello de botella rara vez es técnico — es alineación organizacional y confianza en testing.
¿Deberíamos parar el desarrollo de capacidades durante la migración?
No. El negocio no se detiene. Corre migración como track paralelo: 70% capacidades, 30% migración. Si migración toma 100% de capacidad, estás extrayendo muy agresivamente.
¿Qué pasa si terminamos con un monolito distribuido?
Un monolito distribuido ocurre cuando servicios comparten BD o deployan juntos. La prevención es la cura: aplica database-per-service y pipelines de deploy independientes. Arreglarlo después es doloroso; préviene desde el inicio.
Recursos Relacionados
Arquitectura de Microservicios — Cuándo Usarla y Cuándo No
Guía práctica de microservicios: beneficios, trade-offs, patrones comunes y cuándo elegirlos sobre monolitos. Cubre estrategias de descomposición y complejidad operativa.
GuideDomain-Driven Design (DDD): Guía Práctica
Aprende los fundamentos de DDD: bounded contexts, entidades, value objects, aggregates, y cómo modelar dominios de negocio complejos en código.
GuidePrincipios SOLID Explicados con Ejemplos
Aprende los cinco principios SOLID con ejemplos prácticos de código: Responsabilidad Única, Abierto/Cerrado, Sustitución de Liskov, Segregación de Interfaces e Inversión de Dependencias.
RecipePatrones de Comunicación entre Microservicios
Elige entre patrones de comunicación síncronos y asíncronos para arquitecturas de microservicios resilientes.
RecipeDescubrimiento de servicios
Implementa service discovery con health checks, resolución DNS-based y service registries para ambientes en vivo de microservicios.
PatternPatron de Almacen Externo de Configuracion
Centraliza la configuracion de aplicaciones fuera de los artefactos de despliegue para soportar actualizaciones en vivo y gestion multi-entorno.