Cuándo NO migrar a microservicios
Antes de hablar de cómo, hablemos de cuándo no deberías:
- Tu equipo tiene menos de 10 ingenieros: El overhead operacional de microservicios (networking, observabilidad, deploy independiente) supera los beneficios.
- Tu monolito funciona bien: "Microservicios" no es sinónimo de "moderno". Un monolito bien estructurado es más fácil de mantener.
- No tienes problemas de escala diferenciada: Si todas las partes de tu sistema necesitan la misma capacidad, microservicios añade complejidad sin beneficio.
La razón correcta para migrar es: necesitas escalar, deployar o evolucionar partes del sistema de forma independiente, y tu monolito se ha convertido en un cuello de botella organizacional.
Señales de que es hora de migrar
- Deploys son arriesgados: Un cambio en el módulo de pagos requiere redesployar todo el sistema, incluyendo el catálogo de productos.
- Equipos se bloquean mutuamente: El equipo de checkout espera a que el equipo de inventario termine su feature porque comparten la misma base de código.
- Escala desproporcional: Tu API de búsqueda recibe 100× más tráfico que tu módulo de reportes, pero ambos corren en los mismos servidores.
- Ciclos de release largos: Los releases son mensuales porque coordinar todos los cambios en un solo deploy es complejo.
Estrategia: Strangler Fig, no Big Bang
Nunca reescribas todo de cero. La estrategia Strangler Fig (higuera estranguladora) consiste en:
- Identificar el dominio más independiente con el mayor beneficio de separación
- Extraerlo como servicio detrás de la misma API
- Enrutar tráfico gradualmente (10% → 50% → 100%)
- Repetir con el siguiente dominio
Paso 1: Bounded Contexts
Antes de tocar código, mapea tus bounded contexts con el equipo:
Monolito actual:
├── Users & Auth → Contexto: Identity
├── Product Catalog → Contexto: Catalog
├── Shopping Cart → Contexto: Cart
├── Checkout & Payments → Contexto: Payments
├── Order Fulfillment → Contexto: Fulfillment
├── Notifications → Contexto: Notifications
└── Reporting → Contexto: Analytics
Cada contexto tiene datos propios, reglas propias y un equipo responsable. Si dos contextos comparten tablas en la base de datos, necesitas resolver esa dependencia antes de separarlos.
Paso 2: La primera extracción
Elige el servicio que:
- Tiene menor acoplamiento con el resto del sistema
- Tiene mayor beneficio de escala independiente
- El equipo responsable está motivado y disponible
En la mayoría de los casos, Notifications o Analytics son buenos candidatos iniciales: tienen pocas dependencias entrantes, no están en el path crítico de la transacción, y el equipo puede fallar sin afectar al checkout.
Paso 3: El patrón de comunicación
Para la comunicación entre servicios:
| Patrón | Cuándo usarlo | Cuándo evitarlo |
|---|---|---|
| HTTP/REST síncrono | Consultas simples, baja latencia requerida | Operaciones que pueden fallar y necesitan retry |
| Eventos asincrónicos (Kafka/NATS) | Notificaciones, analytics, procesos eventual | Cuando necesitas respuesta inmediata |
| gRPC | Comunicación interna de alto volumen | APIs públicas, compatibilidad con browsers |
Regla de oro: Si el servicio A necesita esperar la respuesta de B para continuar, usa síncrono. Si A solo necesita que B eventualmente se entere, usa eventos.
Antipatrones que hemos visto
El microservicio distribuido monolítico
Servicio A → Servicio B → Servicio C → Servicio D → Base de datos compartida
Si todos tus microservicios comparten una base de datos y se llaman en cadena síncrona, no tienes microservicios — tienes un monolito distribuido con latencia extra y más puntos de falla.
El nano-servicio
Un servicio por cada entidad de la base de datos. UserService, AddressService, PhoneService... Si un cambio de dominio requiere modificar 5 servicios, la granularidad es excesiva.
CRUD como servicio
Si tu "microservicio" es un wrapper de una tabla con endpoints GET/POST/PUT/DELETE y cero lógica de negocio, no es un servicio — es una capa de acceso a datos innecesaria.
Lo que necesitas desde el día 1
No extraigas el primer servicio sin tener esto resuelto:
Observabilidad
- Distributed tracing (Jaeger/Tempo): Cada request debe ser trazable a través de todos los servicios
- Logs centralizados (ELK/Loki): Un solo lugar para buscar errores
- Métricas (Prometheus/Grafana): Latencia p50/p95/p99 por servicio, tasa de errores, saturación
CI/CD por servicio
Cada servicio tiene su propio pipeline:
- Build independiente
- Tests independientes
- Deploy independiente
- Rollback independiente
Si un deploy de Payments falla, no debería afectar a Catalog.
Service mesh o API gateway
Para routing, rate limiting, circuit breaking y mTLS entre servicios. Opciones:
- Istio/Linkerd para Kubernetes
- Kong/Traefik como API gateway
- NGINX con configuración manual (viable para < 10 servicios)
Caso real: De 6 semanas a releases diarios
Un marketplace con el que trabajamos tenía:
- Monolito de 4 años en Python/Django
- 15 ingenieros en 3 equipos
- Releases cada 6 semanas con ventana de deploy de 4 horas
- 3 incidentes mayores en los últimos 6 meses por deploys fallidos
Lo que hicimos
- Mes 1: Mapeamos bounded contexts, identificamos 6 dominios, priorizamos Notifications y Analytics como primeras extracciones
- Mes 2-3: Extraímos Notifications como servicio en Go con NATS para eventos. Montamos observabilidad (Grafana + Jaeger + Loki)
- Mes 3-4: Extraímos Payments como servicio en Go con PostgreSQL propio. Implementamos saga pattern para coordinación con el monolito
- Mes 4-5: CI/CD independiente por servicio en GitHub Actions. Feature flags con LaunchDarkly
- Mes 5-6: Extraímos Catalog. A este punto el equipo ya dominaba el patrón
Resultados
| Métrica | Antes | Después |
|---|---|---|
| Frecuencia de deploy | Cada 6 semanas | Diario |
| Ventana de deploy | 4 horas | < 5 minutos |
| Incidentes por deploy | ~0.5 | < 0.05 |
| Tiempo de onboarding | 3 semanas | 1 semana (por servicio) |
| Escala del equipo | 15 → bloqueados | 15 → 3 squads autónomos |
Impacto de negocio
Cómo Numoru vende una migración
La oferta productizada es un Architecture Fitness Assessment ($9,500, 3 semanas) que produce un yes/no sobre si microservicios tienen sentido y, si sí, la lista priorizada de seams. Desde ahí, un retainer por fases maneja el programa strangler — típicamente 9-18 meses — con exit criteria claros en lugar de un rebuild abierto.
Ticket por comprador (Numoru, USD)
Martin Fowler — Strangler Fig / MonolithFirst
DORA / State of DevOps
Migración strangler para SaaS de 30 ingenieros (12 meses)
| Migración (one-time) | −$140,000 |
| MSP (12 mo × $6k) | −$72,000 |
| Overhead de infra | −$40,000 |
| Velocity de revenue capturado | +$780,000 |
| Runway de hiring extendido | +$240,000 |
| Contribución neta año 1 | +$768,000 |
- Review DDD
- Mapeo deploy-graph
- Priorización seams
- Exit criteria + scorecard
- Extracción seam por seam
- Observabilidad-first
- Enablement platform team
- Revisión ejecutiva trimestral
- Planes de rollback
- Hardening CI/CD + infra
- Review de costo + right-sizing
- Audit trimestral
- On-call incidentes
Conclusión
La migración a microservicios no es un proyecto técnico — es un proyecto organizacional que usa técnicas técnicas. Si tu estructura de equipos no cambia, tus servicios terminarán replicando las dependencias del monolito (Ley de Conway).
Empieza por el dominio más independiente, invierte en observabilidad antes de extraer nada, y recuerda: el objetivo no es tener microservicios — es tener equipos que pueden entregar valor de forma independiente.