El monolito modular es la arquitectura que las startups ignoran

Un equipo de veinte personas con catorce servicios sin equipo de plataforma tiene un monolito distribuido, no microservicios. El monolito modular entrega disciplina de boundaries en un solo desplegable — comprando opcionalidad sin pagar el impuesto distribuido.

Ingeniería9 min de lectura
ArquitecturaMonolito modularMicroserviciosBackendStartups
Compartir

Una startup con ocho ingenieros, un pipeline de deployment y ningún equipo de plataforma dedicado no necesita microservicios. Necesita un codebase con boundaries lo suficientemente fuertes para sobrevivir al crecimiento — boundaries que vivan dentro de un solo proceso, reforzados por el sistema de módulos y no por la red. El monolito modular es esa arquitectura: un artefacto desplegable, múltiples dominios aislados, y la opción de extraer un servicio después sin reescribir nada.

La industria pasó una década tratando a los microservicios como una insignia de madurez. Equipos de cinco ingenieros dividieron su producto en doce servicios, y luego pasaron la mitad de su tiempo gestionando comunicación inter-servicio, coordinación de deployments y debugging distribuido. La arquitectura que debía habilitar velocidad creó overhead de coordinación que ralentizó a los equipos por debajo de la velocidad que tenían con su monolito original. La corrección ya está en marcha, y no es una retirada — es el reconocimiento de que los boundaries importan más que la topología de deployment.

Un monolito modular compra opcionalidad sin costo distribuido

La propuesta de valor central del monolito modular es opcionalidad a bajo costo. Cada módulo es un bounded context — posee su lógica de dominio, sus tablas de base de datos y su API pública. Otros módulos interactúan solo a través de esa API. Los detalles de implementación quedan ocultos detrás de visibilidad a nivel de lenguaje: clases package-private, namespaces internos o exports con scope de módulo según el runtime.

Esto entrega los mismos beneficios cognitivos que los microservicios: ownership clara, interfaces definidas, desarrollo independiente dentro de los boundaries del módulo. Pero evita el costo operacional de los sistemas distribuidos: ningún service mesh, ninguna autenticación inter-servicio, ningún distributed tracing requerido desde el día uno, ningún dolor de cabeza con consistencia eventual, ninguna coordinación de deployment entre doce repositorios.

La opcionalidad aparece cuando un módulo genuinamente necesita extracción. Como el módulo ya tiene una API pública limpia y posee sus datos, promoverlo a un servicio independiente es una decisión de deployment — no una reescritura. El boundary se estableció en el codebase mucho antes de que la red necesitara reforzarlo.

Los boundaries importan antes que la red

El problema más difícil en sistemas distribuidos es encontrar los boundaries correctos. La mayoría de equipos que adoptan microservicios temprano se equivocan en los boundaries — porque la comprensión del dominio al mes tres de un producto es superficial comparada con la del mes dieciocho. Cambiar un boundary de módulo dentro de un monolito es una tarea de refactoring. Cambiar un boundary de servicio en un sistema distribuido es un proyecto de migración.

Un monolito modular provee un entorno de bajo costo para explorar y ajustar boundaries. El módulo de billing puede empezar poseyendo tres tablas y exponiendo cinco operaciones. Seis meses después, cuando el equipo descubre que la generación de facturas pertenece a un contexto separado del procesamiento de pagos, la separación ocurre en el mismo repositorio, en el mismo pipeline de CI, con el mismo artefacto de deployment. Sin nueva infraestructura, sin nuevo service discovery, sin nuevos modos de fallo.

Los mecanismos de enforcement que mantienen la honestidad de los boundaries:

  • Visibilidad a nivel de lenguaje: los módulos exponen interfaces públicas y mantienen clases de implementación privadas. En TypeScript, barrel exports con archivos index.ts explícitos. En Java, clases package-private detrás de una fachada pública. En Go, tipos no exportados dentro de paquetes internal.
  • Tests de arquitectura: reglas automatizadas que corren en CI y fallan el build cuando un módulo importa desde los internals de otro módulo. ArchUnit en ecosistemas JVM, dependency-cruiser o eslint-plugin-boundaries en Node.js, Nx workspace constraints en monorepos.
  • Ownership de datos: cada módulo posee su schema de base de datos. Sin joins cross-module. Si el módulo de orders necesita datos de clientes, llama a la API del módulo de customers — nunca consulta la tabla de customers directamente.

Sin enforcement, un monolito modular degenera en un monolito regular en cuestión de meses. La diferencia entre ambos no es la estructura de carpetas — es si las violaciones de boundary causan fallos en el build.

Un ejemplo práctico: una aplicación SaaS con cuatro módulos — auth, billing, projects y notifications. Cada módulo vive en su propio directorio top-level, exporta una interfaz pública a través de un barrel index.ts, y posee un schema de base de datos dedicado. El módulo projects necesita verificar límites de suscripción antes de crear un nuevo proyecto — llama a billing.getActivePlan(orgId) a través de la interfaz publicada. Nunca consulta la tabla billing.subscriptions directamente. Cuando el equipo de billing migra después de planes fijos a medición por uso, el cambio es interno al módulo de billing. El módulo de projects sigue llamando la misma función y recibe la misma forma de respuesta. Sin coordinación, sin renegociación de contrato, sin dependencia de deployment.

Un deployment puede contener muchos dominios

Una objeción común al monolito modular es el acoplamiento de deployment: si un módulo tiene un bug, toda la aplicación hace rollback. Esto es cierto — y para la mayoría de equipos bajo veinte ingenieros, es aceptable. La alternativa — doce pipelines de deployment independientes con su propio CI, sus propias estrategias de rollback y su propia coordinación de releases — crea más overhead de deployment del que cuesta el rollback compartido ocasional.

El monolito modular abraza este trade-off explícitamente:

  • Deployment compartido, desarrollo aislado. Cada módulo tiene su propia suite de tests que corre de forma independiente. Un cambio en el módulo de billing no dispara tests del módulo de notificaciones.
  • Proceso compartido, datos aislados. Separación lógica de base de datos (schemas separados o tablas con prefijos) previene el acoplamiento accidental cross-module en la capa de datos.
  • Repositorio compartido, ownership aislado. Reglas de CODEOWNERS asignan módulos a equipos específicos. Pull requests que tocan múltiples módulos requieren reviews de cada equipo propietario.

Para un equipo que despliega entre una y diez veces al día con releases coordinados, este modelo funciona. Cuando el equipo crece a cincuenta ingenieros en múltiples zonas horarias, y la cadencia de deployment de un equipo diverge a docenas de veces por hora, ese módulo específico se convierte en candidato a extracción — no antes.

Extraer servicios solo cuando la evidencia aparece

La extracción prematura de servicios es el error arquitectónico más caro que puede cometer una startup en crecimiento. El disparador correcto para la extracción no es "este módulo es importante" ni "este módulo es complejo". Los disparadores correctos son restricciones concretas y medibles:

  • Divergencia de escalado: un módulo necesita diez veces más cómputo que el resto, y escalar toda la aplicación horizontalmente para satisfacer la carga de un módulo desperdicia recursos.
  • Independencia de deployment: un equipo está bloqueado por el calendario de releases de otro equipo a pesar de boundaries limpios, y el costo de coordinación excede el costo de extracción.
  • Aislamiento de runtime: una falla en un módulo (memory leak, spike de CPU, excepción no manejada) no debe afectar otros módulos bajo ninguna circunstancia — requisitos regulatorios o contractuales exigen aislamiento físico.
  • Divergencia tecnológica: un módulo requiere un runtime fundamentalmente diferente — cómputo GPU, otro lenguaje, otro modelo de persistencia — que no puede coexistir en el proceso actual.

Si ninguna de estas condiciones se cumple, la extracción crea overhead sin entregar valor. Un equipo que extrae un servicio "porque podría necesitar escalar independientemente algún día" paga el impuesto de sistemas distribuidos hoy a cambio de un beneficio hipotético que quizás nunca llegue.

La secuencia de decisión: comenzar con un monolito por velocidad, refactorizar hacia un monolito modular cuando aparece fricción de ownership (conflictos de merge, boundaries difusos, cambios en un área rompiendo otra), y extraer servicios solo cuando evidencia concreta justifique el costo operacional.

¿Cómo funcionan los boundaries del monolito modular en la práctica?

El diseño de boundaries entre módulos es donde la mayoría de equipos fallan — no porque el concepto sea difícil, sino porque el enforcement requiere tooling, no solo documentación.

¿Cómo deben comunicarse los módulos sin acoplamiento?

Los módulos se comunican a través de interfaces publicadas — funciones exportadas, contratos de eventos u objetos de API interna. Un módulo de billing expone una función createInvoice(customerId, lineItems). El módulo que llama conoce el contrato pero no la implementación. Si el módulo de billing cambia su persistencia interna de Postgres a un sistema basado en ledger, ningún otro módulo se entera. Los eventos proveen un acoplamiento aún más laxo: el módulo de orders publica un evento OrderCompleted, y el módulo de billing se suscribe — ninguno conoce al otro directamente.

¿Qué pasa cuando dos módulos necesitan los mismos datos?

Los datos compartidos son la forma más rápida de destruir boundaries entre módulos. Cuando dos módulos necesitan la misma información, el módulo propietario la expone a través de una API de lectura — no de una tabla compartida. Si el rendimiento exige colocación, el módulo consumidor mantiene una proyección local (un read model) actualizada por eventos del módulo fuente. Esto agrega complejidad — pero menos complejidad que una base de datos distribuida entre dos microservicios.

¿Cómo se previene la erosión de boundaries con el tiempo?

El enforcement automatizado es el único mecanismo confiable. Un boundary documentado en un wiki será violado dentro de semanas. Un boundary reforzado por un check de CI que falla el build dura tanto como el test corra. La inversión es pequeña — un solo archivo de test de arquitectura por módulo que declara sus dependencias permitidas — y el retorno es integridad estructural permanente.

Una visión opuesta

Un argumento frecuente sostiene que los microservicios resuelven un problema organizacional más que técnico. Los equipos que despliegan de forma independiente, eligen su propia tecnología y poseen su servicio end-to-end toman mejores decisiones más rápido — y esa autonomía se compone a medida que la organización crece. Un monolito modular, en contraste, fuerza a todos los equipos al mismo ritmo de deployment, el mismo lenguaje y el mismo runtime, creando cuellos de botella que crecen con el tamaño del equipo.

Este argumento es correcto — para organizaciones con cincuenta o más ingenieros trabajando sobre boundaries de dominio estables y bien comprendidos. El calificador clave es "estables". Si los boundaries aún están cambiando (como siempre ocurre en los primeros dos años de un producto), los microservicios fijan esos boundaries en contratos de red que son caros de cambiar. El beneficio organizacional de la autonomía solo se materializa cuando los dominios son lo suficientemente estables para que los equipos rara vez necesiten coordinar cambios de boundaries. Para equipos bajo veinte ingenieros, el costo de coordinación dentro de un monolito modular es una conversación de standup — no un proyecto de migración de varias semanas.

Lo que importa recordar

  • Los microservicios resuelven un problema de escalamiento organizacional, no técnico. Equipos bajo veinte ingenieros rara vez tienen el problema organizacional que los microservicios abordan.
  • Un monolito modular da disciplina de boundaries dentro de un solo desplegable, comprando opcionalidad para extraer servicios después sin pagar el impuesto distribuido hoy.
  • Los boundaries se refuerzan con tooling (tests de arquitectura, modificadores de visibilidad, reglas de ownership de datos) — la documentación sola se degrada en semanas.
  • Extraer un servicio solo cuando aparece evidencia concreta: divergencia de escalado, requisitos de independencia de deployment, necesidades de aislamiento de runtime o divergencia tecnológica.
  • Un equipo de veinte personas con catorce servicios y sin equipo de plataforma no tiene arquitectura de microservicios — tiene un monolito distribuido con latencia.
  • El problema más difícil en sistemas distribuidos es encontrar los boundaries correctos. Un monolito modular provee un entorno de bajo costo para descubrirlos antes de comprometerse con enforcement por red.

Conclusión

La decisión arquitectónica madura para la mayoría de startups es menos desplegables, no más. Un solo artefacto con boundaries internos fuertes permite a un equipo en crecimiento moverse rápido, refactorizar barato y postergar el impuesto de sistemas distribuidos hasta que el negocio realmente genere las restricciones que lo justifiquen. La pregunta antes de extraer un servicio no es "¿podría esto escalar independientemente?" — es "¿el costo de mantenerlo en proceso es mayor que el costo de operarlo como un sistema separado?" Para la mayoría de los equipos, la mayor parte del tiempo, la respuesta es no. Las organizaciones que construyen los cimientos más sólidos son las que resisten la urgencia de distribuir dominios que aún no comprenden.

Artículos relacionados

Paleta de comandos

Buscá un comando para ejecutar...