</>Oriol Martí
← Blog

Monolito modular antes que microservicios

· 5 min de lectura

He trabajado en monolitos que daban miedo tocar y en sistemas de microservicios donde un cambio pequeño exigía coordinar tres despliegues. Las dos cosas son posibles, y casi nunca la culpa es del patrón: es de cuándo y por qué se eligió.

Mi posición es sencilla: partir un sistema antes de tener sus límites claros multiplica el acoplamiento en lugar de reducirlo. Los microservicios no hacen que el código esté mejor organizado. Hacen que cada error de organización sea más caro, porque ahora pasa por la red.

Qué resuelven de verdad los microservicios

Antes de decidir, conviene ser honesto sobre qué problema se quiere resolver. Los microservicios resuelven bien estos:

  • Equipos que se pisan. Varios equipos que tocan el mismo código y tienen que coordinar cada despliegue.
  • Ritmos distintos. Una parte del sistema cambia diez veces al día y otra, una vez al trimestre.
  • Necesidades de escalado distintas. Un componente necesita mucha más capacidad que el resto, o un perfil de recursos muy diferente.
  • Aislamiento de fallos. Que un problema en una parte no tumbe todo lo demás.
  • Tecnologías distintas. Una pieza tiene un buen motivo para estar en otro lenguaje o con otra base de datos.

Fíjate en que casi todos son problemas de organización y de operación, no de diseño del código. Si el código está desordenado, partirlo en servicios no lo ordena: reparte el desorden entre varios repositorios y le añade latencia.

Señales de que toca partir

Partiría un sistema cuando aparecen varias de estas señales a la vez:

  • El despliegue es el cuello de botella. Los equipos esperan unos a otros para salir a producción, o un cambio pequeño exige probarlo todo.
  • Hay una frontera que ya funciona como tal. Un módulo con un contrato estable, que casi nunca necesita cambiar a la vez que el resto.
  • El perfil de carga es muy distinto. Una parte recibe picos que no tienen nada que ver con el resto del sistema.
  • Un fallo localizado tumba demasiado. Un error en una funcionalidad secundaria deja caída la principal.
  • Hay un equipo dispuesto a ser el dueño. Un servicio sin dueño claro se convierte en el que nadie quiere tocar.

Señales de que todavía no

Y esperaría si reconozco alguna de estas:

  • Los límites cambian cada mes. Si todavía no sabes dónde acaba “pedidos” y empieza “pagos”, cualquier frontera que pongas ahora será la equivocada, y moverla entre servicios cuesta mucho más que moverla entre carpetas.
  • Todo cambio toca varias partes. Si cada funcionalidad nueva modifica tres módulos, partirlos convierte cada funcionalidad en tres despliegues coordinados.
  • El equipo es pequeño. Un equipo de pocas personas con diez servicios dedica más tiempo a mantener la infraestructura que a construir producto.
  • No hay base operativa. Sin despliegue automatizado, logs centralizados, trazas y alertas, depurar un fallo que cruza servicios es adivinar.
  • El motivo es “así es como se hace”. Es el peor motivo, y es más habitual de lo que parece.

El camino intermedio: módulos con fronteras explícitas

Entre el monolito desordenado y los microservicios hay una opción que suele ser la mejor durante mucho tiempo: el monolito modular.

Es una sola aplicación y un solo despliegue, pero organizada por dentro como si fueran servicios:

┌───────────────────────────── Aplicación (un despliegue) ─────────────────────────────┐
│                                                                                      │
│  ┌─────────────┐   API pública   ┌─────────────┐   API pública   ┌─────────────┐    │
│  │  Catálogo   │ ◀─────────────▶ │   Pedidos   │ ◀─────────────▶ │    Pagos    │    │
│  │ (sus tablas)│                 │ (sus tablas)│                 │ (sus tablas)│    │
│  └─────────────┘                 └─────────────┘                 └─────────────┘    │
│                                                                                      │
│  Prohibido: importar código interno de otro módulo o leer sus tablas                 │
└──────────────────────────────────────────────────────────────────────────────────────┘

Las reglas que lo hacen funcionar:

  • Cada módulo expone una API pública y esconde todo lo demás. Los otros módulos solo pueden usar esa API.
  • Cada módulo es dueño de sus datos. Nadie lee las tablas de otro módulo directamente.
  • Las fronteras se comprueban de forma automática, con reglas de lint o tests de arquitectura que fallan si alguien importa algo interno. Una convención que solo vive en la wiki acaba ignorándose.
  • La comunicación puede ser por eventos internos donde tenga sentido, igual que lo sería entre servicios.

Lo mejor de este enfoque es que mantiene abierta la decisión. Si un módulo tiene una frontera estable y aparece un motivo real para separarlo, extraerlo como servicio es casi mecánico: la API pública se convierte en un contrato HTTP o de eventos, y sus tablas ya eran solo suyas. En cambio, si te equivocaste de frontera, corregirla es un refactor dentro de un mismo repositorio, no una migración entre sistemas.

El coste operativo real de cada servicio nuevo

Un servicio nuevo no es solo una carpeta más. Es todo esto, multiplicado por cada servicio:

Concepto Monolito modular Cada servicio nuevo
Pipeline de CI/CD Uno Uno más
Infraestructura y entornos Compartida Propia (o plantillas que mantener)
Llamadas entre partes Una función en memoria Red: latencia, timeouts, reintentos
Consistencia de datos Transacciones Sagas, eventos, consistencia eventual
Depuración Una traza local Trazado distribuido
Versionado de contratos Refactor en un mismo commit Compatibilidad hacia atrás entre despliegues
Monitorización y alertas Un conjunto Un conjunto por servicio

Ninguna de estas filas es imposible. Pero todas cuestan tiempo de ingeniería que no se dedica al producto. Ese coste solo compensa cuando el problema que resuelve el servicio (equipos independientes, escalado, aislamiento) es mayor que él.

Cómo lo decido

Cuando me toca decidir si partir algo, me hago tres preguntas:

  1. ¿Qué problema concreto resuelve la separación? Si la respuesta es “el código está desordenado”, el problema no se arregla con red.
  2. ¿La frontera lleva tiempo estable? Si sigue moviéndose, todavía no es momento de convertirla en un contrato entre servicios.
  3. ¿Puedo pagar el coste operativo? Pipelines, observabilidad, guardias y contratos versionados, para siempre.

Si las tres respuestas son claras, separo sin dudarlo. Si alguna no lo es, ordeno primero por dentro con un monolito modular, y dejo que el sistema me diga dónde están sus fronteras de verdad.

Los microservicios son una herramienta excelente para escalar una organización. Para ordenar el código, la herramienta sigue siendo la misma de siempre: buenos límites, aunque estén dentro del mismo despliegue.