Monolito modular o microservicios: un marco para decidir sin dogmas
Cuándo se justifican los microservicios y cuándo un monolito modular te da el 80% del beneficio por el 20% del costo. Con un marco para decidir y un ejemplo en Laravel.
Imagina un equipo que arranca un producto nuevo. Antes de que se registre el primer usuario, el diagrama de arquitectura ya tiene diez servicios, un broker de mensajes y un orquestador. Se ve moderno. Se ve escalable.
Tres meses después, un cambio simple, agregar un campo a una factura, toca cuatro servicios, exige cuatro despliegues coordinados y falla de una forma que nadie logra rastrear. Cada llamada que antes era una llamada a función ahora es una petición de red, y el equipo gasta más tiempo en que las piezas se hablen entre sí que en construir el producto.
Esto es el monolito distribuido: todo sigue acoplado, pero ahora a través de la red. Es lo que pasa cuando la arquitectura se elige por moda y no por necesidad.
Tras más de 20 años diseñando sistemas, mi posición es esta: empieza con un monolito modular y extrae servicios solo cuando una razón concreta lo exija. Este artículo no es una defensa del monolito. Es un marco para decidir con criterios que puedes verificar. La pregunta útil nunca fue "¿microservicios o monolito?", sino: ¿qué problema concreto estoy resolviendo y cuánto cuesta resolverlo así?
Dos cosas que se confunden
Para responder esa pregunta, primero hay que tener claro qué estamos comparando.
- Monolito modular: una sola unidad de despliegue, pero con módulos de límites claros, contratos públicos explícitos y dependencias controladas.
- Microservicios: varias unidades de despliegue independientes, cada una con su propio ciclo de vida y, idealmente, su propia base de datos.
Un monolito modular no es "el monolito de siempre". La diferencia es la disciplina: los módulos se comportan como si fueran servicios, pero se comunican dentro del mismo proceso.
Entonces, si el monolito modular ya te da límites, ¿qué compras exactamente al ir más lejos?
La factura que casi nadie lee
Pasar a microservicios no es gratis. Lo que ganas en independencia lo pagas en:
- Red: latencia, reintentos, timeouts y fallos parciales que antes no existían.
- Consistencia de datos: se acaban las transacciones ACID entre dominios. Entran las sagas, las compensaciones y la consistencia eventual.
- Observabilidad: sin tracing distribuido, logs correlacionados y métricas por servicio, depurar un error se vuelve arqueología.
- Despliegue y operación: pipelines por servicio, versionado de contratos, y configuración y secretos multiplicados.
- Carga cognitiva: más piezas que entender, más límites que mantener y más lugares donde un cambio rompe el trabajo de otro equipo.
Esos costos son aceptables cuando a cambio compras algo valioso. Si no, es deuda con intereses.
Lo cual plantea la pregunta justa: ¿cuándo vale la pena la compra?
Cuándo los microservicios se ganan su lugar
Hay razones reales para extraer un servicio:
- Equipos que necesitan desplegar de forma independiente. Si dos equipos se bloquean mutuamente en cada release, el límite de servicio resuelve un problema organizacional real.
- Perfiles de escalado muy distintos. Un componente que necesita 50 réplicas mientras el resto necesita 2.
- Aislamiento de fallos. Una parte que no debe tumbar al resto, como el procesamiento de pagos o la ingesta masiva de datos.
- Requisitos de cumplimiento o seguridad. Datos que deben vivir aislados, con sus propios controles de acceso y auditoría.
- Tecnología distinta por necesidad real, no por preferencia: por ejemplo, un componente de cómputo intensivo que rinde mejor en otro lenguaje.
Si tu razón no está en esta lista, probablemente puedes esperar. Y mientras esperas, conviene un diseño que mantenga la puerta abierta.
Cómo construir un monolito del que puedas salir después
El objetivo de un monolito modular es que una extracción futura sea aburrida. Estas son las reglas que lo hacen funcionar:
- Un módulo por contexto de negocio (Billing, Inventory, Users), alineado con los límites del dominio, de la mano de DDD (Domain-Driven Design).
- Un contrato público explícito por módulo. Los demás módulos solo hablan con esa interfaz, nunca con los internos.
- Sin acceso a tablas de otro módulo. Un módulo no hace JOIN contra las tablas de otro. La información se pide a través del contrato.
- Eventos internos para desacoplar. Cuando algo ocurre en un módulo, publica un evento; los demás reaccionan sin conocerse entre sí.
- Reglas verificadas automáticamente, no por buena voluntad.
Esa última regla es la que los equipos se saltan, y es la que más importa. Así se ve en la práctica.
Un ejemplo en Laravel
Una estructura sencilla:
app/
└── Modules/
├── Billing/
│ ├── Contracts/ # lo único que otros módulos pueden usar
│ ├── Internal/ # detalles privados del módulo
│ └── Events/
└── Shipping/
├── Contracts/
├── Internal/
└── Events/
Y una prueba de arquitectura con Pest que hace cumplir el límite:
arch('Billing no usa los internos de Shipping')
->expect('App\Modules\Billing')
->not->toUse('App\Modules\Shipping\Internal');
arch('Shipping no usa los internos de Billing')
->expect('App\Modules\Shipping')
->not->toUse('App\Modules\Billing\Internal');
Si alguien rompe el límite, el pipeline de CI falla. Un límite que nadie verifica termina siendo una sugerencia. Herramientas como Deptrac cumplen el mismo propósito.
Las preguntas antes de dividir
Ahora supón que un módulo empieza a doler. Antes de extraer un servicio, responde estas preguntas:
| Pregunta | Si la respuesta es "no" |
|---|---|
| ¿Hay un equipo que necesita desplegar sin coordinarse con otros? | Probablemente aún no necesitas el servicio |
| ¿Este componente tiene una carga o un perfil de escalado claramente distinto? | Escalar todo el monolito suele salir más barato |
| ¿Existe un requisito de aislamiento de fallos o de cumplimiento? | Un módulo con buenos límites puede bastar |
| ¿Tenemos observabilidad madura y despliegue automatizado? | Resuelve eso primero, o el costo operativo te ahogará |
| ¿El límite del dominio es estable y está bien entendido? | Extraer un límite equivocado es caro de revertir |
La última es la más subestimada. Un límite mal trazado dentro de un monolito se corrige con un refactor. Un límite mal trazado entre servicios se corrige con una migración.
Cómo puede evolucionar la historia
La buena noticia es que esta decisión no es binaria ni permanente. Un camino sano se ve así:
- Empieza con módulos bien separados y reglas automatizadas.
- Mide: tiempos de despliegue, bloqueos entre equipos, puntos calientes de carga.
- Cuando un módulo cumple una de las razones de la lista, extráelo. Como ya tiene un contrato público y no comparte tablas, la extracción es mecánica: el contrato se convierte en una API o en un conjunto de eventos.
- Aplica el patrón strangler (estrangulador): redirige el tráfico poco a poco hacia el nuevo servicio sin reescribirlo todo.
Así, la decisión sobre microservicios se toma con datos y con el costo de la migración ya reducido.
Donde la historia sale mal
Incluso con un buen plan, algunos errores aparecen una y otra vez:
- Una base de datos compartida entre "microservicios". Es un monolito con latencia añadida.
- Servicios demasiado pequeños (nanoservicios). El costo de coordinación supera el beneficio.
- Extraer antes de entender el dominio. Los límites se descubren con el tiempo, no en una pizarra.
- Ignorar la organización. La arquitectura termina reflejando la estructura de los equipos; si no coinciden, aparece la fricción.
De vuelta con aquel equipo
Imagina al mismo equipo, empezando de nuevo. Esta vez arrancan con un monolito modular, hacen cumplir los límites en CI y miden. El campo de la factura es un cambio en un solo módulo. Meses después, cuando un componente necesita escalar muy por encima del resto, lo extraen, y como el contrato ya existe, les toma días en lugar de una reescritura.
Ese es todo el argumento:
- Un monolito modular es el punto de partida razonable para la mayoría de los productos.
- Los microservicios son una herramienta con costos reales; úsala cuando a cambio compras algo concreto.
- Haz cumplir los límites de forma automática, para que la opción de extraer servicios siga abierta.
- Decide con criterios medibles, no con tendencias.
¿Qué criterios usa tu equipo para decidir cuándo extraer un servicio? Me encantaría leer tu experiencia en los comentarios.