Software Engineering15 de julio de 20268 min read

Del monolito a los microservicios: cuándo dar el salto

Los microservicios no son una insignia de madurez. Así puedes saber cuándo una división realmente vale la pena, cuánto cuesta de verdad y cómo migrar sin romper todo.

Por Innovation T Team


En algún momento del camino, «nos estamos pasando a los microservicios» se convirtió en una manera de señalar que un equipo había madurado. Rara vez sale tan limpio. Muchos equipos han hecho añicos una aplicación perfectamente sana en una docena de servicios, han triplicado su carga operativa y han descubierto que el problema original nunca fue la arquitectura. La pregunta honesta no es «monolito o microservicios». Es «qué estamos intentando resolver en realidad, y si un sistema distribuido es la forma más barata de resolverlo».

Esta guía defiende una opción por defecto aburrida (empezar con un monolito bien estructurado), y luego expone las señales que hacen que dividir valga la pena, los costos de los que nadie te advierte y un patrón de migración que te permite avanzar sin una reescritura arriesgada.

Empieza con un monolito modular

Para la mayoría de los productos, la primera arquitectura correcta es una sola aplicación desplegable con límites internos claros. Este es el monolito modular, y no es un compromiso. Es una elección legítima a largo plazo.

Dentro de él, tu código sigue organizado en torno a dominios de negocio: facturación, catálogo, cuentas, notificaciones. Cada módulo posee sus propios datos y expone una interfaz clara a los demás, pero los módulos se comunican mediante llamadas a funciones dentro del proceso, no mediante solicitudes de red. Obtienes casi toda la disciplina de diseño de los microservicios sin casi ninguno de sus impuestos operativos.

Las ventajas son reales. Una sola base de código significa una compilación, un despliegue, un único lugar donde mirar cuando algo falla. Una llamada a una función funciona o lanza una excepción, así que nunca depuras un flujo de trabajo a medio completar disperso en tres servicios. Refactorizar a través de los límites es un renombrado asistido por el compilador, y las transacciones son sencillas porque todo comparte una sola base de datos.

Y lo más importante: un monolito modular limpio mantiene tus opciones abiertas. Cuando trazas límites nítidos entre los módulos desde el principio, cualquiera de ellos puede extraerse más adelante a su propio servicio con mucho menos dolor. La disciplina que inviertes en los límites de los módulos es exactamente la que exigen los microservicios, así que nada de ella se desperdicia. No estás eligiendo el monolito para siempre. Estás eligiendo aplazar la distribución hasta que tengas una razón concreta para ella.

Las señales que de verdad justifican una división

Una división se justifica por la presión organizativa y operativa, no por la estética. Conviene ver más de una de estas señales antes de comprometerte.

Escalado de equipos y costo de coordinación. La razón más duradera para dividir son las personas. Cuando varios equipos contribuyen a la misma base de código y pelean por el mismo canal de despliegue, cada entrega se convierte en una negociación, y la función sin terminar de un equipo bloquea el arreglo urgente de otro. Los servicios permiten que cada equipo posea, despliegue y escale su parte según su propio calendario. Si tienes uno o dos equipos, esta presión todavía no existe, y una división no te ayudará.

Necesidad de despliegues independientes y frecuentes. Cuando una parte del sistema se publica varias veces al día mientras otra cambia una vez por trimestre, agruparlas en un solo despliegue genera fricción. Un módulo de pagos que nunca debe presentar regresiones no quiere que se le vuelva a desplegar cada vez que cambia el texto de marketing. Este es un beneficio genuino, pero solo si tu estrategia de pruebas y de reversión puede manejar muchos despliegues pequeños en lugar de unos pocos grandes.

Necesidades de escalado realmente divergentes. A veces un componente consume mucha CPU y se dispara a muchas veces la carga normal durante una promoción, mientras el resto de la aplicación está inactivo. Si no puedes escalar esa ruta caliente sin pagar por escalar todo lo que la rodea, aislarla como servicio puede valer la pena. Sé honesto aquí: los servidores de aplicaciones modernos y una buena capa de caché resuelven muchos problemas de escalado sin ninguna división. Dividir para ahorrar en cómputo solo vale la pena cuando la divergencia es grande y persistente.

Aislamiento independiente de fallos. Si una integración de terceros lenta o un trabajo en segundo plano descontrolado puede tumbar toda tu aplicación, sacar ese trabajo arriesgado a su propio servicio puede contener el radio de impacto, sobre todo cuando distintas partes del sistema tienen requisitos de fiabilidad muy diferentes.

Fíjate en lo que no está en esta lista: «la base de código se siente grande», «queremos un lenguaje distinto para una función» o «una charla de conferencia dijo que los monolitos no escalan». Esas no son razones. Son estados de ánimo.

Los costos que nadie pone en la presentación

Cada uno de esos beneficios se paga con un costo real y recurrente. Antes de dividir, inclúyelos en tus cuentas.

Ahora la red está en tu ruta crítica. En un monolito, una llamada entre módulos son nanosegundos y no puede fallar por sí sola. Entre servicios, esa misma llamada viaja por una red que es lenta, poco fiable y a veces está caída. Ahora tienes que gestionar tiempos de espera, reintentos, fallos parciales y caídas en cascada. Una solicitud que toca seis servicios acumula seis oportunidades de fallar.

La consistencia de los datos se vuelve difícil. Una sola base de datos te da transacciones gratis. Una vez que cada servicio posee su propio almacén de datos, pierdes la capacidad de actualizar varias cosas de forma atómica, y entras en el mundo de la consistencia eventual, las sagas y las acciones compensatorias. Lograr que un pedido, un recuento de inventario y un pago concuerden en tres servicios sin una transacción compartida es uno de los problemas genuinamente difíciles de los sistemas distribuidos, y es fácil equivocarse de forma sutil.

La observabilidad se vuelve un proyecto, no un archivo de log. En un monolito, una traza de pila te cuenta toda la historia. En un sistema distribuido, una sola acción del usuario deja huellas en muchos servicios y máquinas. Sin trazado distribuido, registro centralizado e identificadores de correlación, depurar se convierte en arqueología. Tienes que construir esta capa antes de necesitarla, no después del primer incidente a las 2 de la madrugada.

La carga operativa se multiplica. Cada servicio necesita su propio canal, despliegue, monitoreo, alertas, secretos y turno de guardia. El desarrollo local ahora significa levantar una flota de servicios o simularla, y la factura de la nube sube. Si vas en esta dirección, planifica tu presupuesto de forma deliberada: nuestra guía de optimización de costos en la nube explica cómo evitar que una huella de múltiples servicios drene tu liquidez en silencio.

Los contratos entre servicios se calcifican. Dentro de un monolito puedes cambiar una interfaz compartida y dejar que el compilador encuentre cada punto que la llama. Entre servicios que pertenecen a equipos distintos, ese mismo cambio se convierte en una negociación de API versionada con compatibilidad hacia atrás que preservar. Diseñar bien esas interfaces importa enormemente, así que vale la pena leer nuestra guía sobre cómo diseñar API que los desarrolladores adoran antes de congelar un solo límite en su lugar.

Migra con la higuera estranguladora, no con una reescritura

Si tienes señales honestas y has calculado los costos, no detengas todo para una reescritura. Reescribir un sistema que funciona es como las empresas pierden un año y no publican nada. Usa en su lugar el patrón de la higuera estranguladora.

El nombre viene de una enredadera que crece alrededor de un árbol anfitrión y va tomando su estructura poco a poco hasta que el original puede retirarse. Aplicado al software, significa que haces crecer la nueva arquitectura alrededor de la vieja, una pieza a la vez, mientras el sistema sigue funcionando.

En la práctica, colocas una capa de enrutamiento, normalmente una puerta de enlace de API o una fachada, delante del monolito para que quienes lo llaman no sepan ni les importe qué hay detrás. Eliges una capacidad bien delimitada, la extraes a un nuevo servicio y rediriges su tráfico en silencio a través de la puerta de enlace. El monolito se encoge en una responsabilidad. Verificas, estabilizas, y solo entonces eliges la siguiente pieza. A lo largo de muchos pasos pequeños y reversibles, el monolito queda estrangulado hasta la nada, o hasta el núcleo que tenga sentido conservar.

La fuerza de este enfoque es que cada paso llega a producción y puede revertirse por sí solo. Nunca estás a un gran salto de la catástrofe, y puedes detenerte en cualquier punto si el monolito restante resulta estar perfectamente bien.

Traza los límites en torno a los dominios, no a las capas

La decisión más importante es dónde van las costuras, y el error más común es cortar a lo largo de capas técnicas: un «servicio de base de datos», un «servicio de lógica de negocio», un «servicio de interfaz». Eso crea servicios que no pueden hacer nada útil por sí solos y que deben parlotear constantemente para completar cualquier tarea real. Obtienes todo el costo de la distribución y nada de la independencia.

Corta a lo largo de los dominios de negocio en su lugar. Un servicio debe poseer una capacidad completa de extremo a extremo, incluidos sus propios datos. «Pedidos», «facturación» e «inventario» son buenos límites porque cada uno representa algo que el negocio hace de verdad. Un servicio bien trazado puede cambiar sus entrañas, su esquema y su lógica sin pedir permiso, porque nada fuera de él depende de ellos.

Un buen límite es aquel donde las interacciones son gruesas en lugar de parlanchinas (unas pocas llamadas significativas, no cientos de diminutas), donde los datos se agrupan de forma natural a un lado de la línea, y donde un solo equipo puede poseer todo el conjunto. Si dos «servicios» actualizan constantemente los datos del otro en el mismo aliento, en realidad son un solo servicio dividido en el lugar equivocado.

Lista de comprobación de preparación

Antes de extraer tu primer servicio, deberías poder responder que sí a la mayoría de estos puntos.

  1. Tienes una señal concreta (escalado de equipos, despliegues independientes o escalado divergente), no solo la sensación de que el monolito es grande.
  2. Tu monolito ya es modular, con límites claros a lo largo de los cuales cortar.
  3. Tienes integración y entrega continuas maduras, de modo que publicar muchos despliegues pequeños es rutinario y no arriesgado.
  4. Tienes registro centralizado, métricas y trazado distribuido ya en marcha, o un plan firme para añadirlos primero.
  5. Tienes un enfoque de infraestructura (contenedores, orquestación, aprovisionamiento automatizado) que hace barato levantar un nuevo servicio.
  6. Tienes un plan para la consistencia de los datos entre servicios y sabes qué flujos de trabajo necesitarán consistencia eventual.
  7. Los equipos están estructurados para poseer los servicios de extremo a extremo, incluidos sus datos y su guardia.
  8. Has calculado el costo operativo y de nube continuo, y el negocio lo acepta.

Si la mayoría de estos son un no, el trabajo de mayor impacto no es dividir. Es ordenar el monolito modular y desarrollar primero los músculos operativos.

Antipatrones que evitar

El monolito distribuido. Servicios que deben desplegarse todos juntos, al unísono, comparten una base de datos y fallan como uno solo. Pagaste el precio completo de la distribución y conservaste todas las desventajas del monolito. Esto es lo peor de ambos mundos y, con diferencia, el fracaso más común.

Dividir para arreglar la calidad del código. El código malo no mejora por repartirse a través de una red. La lógica enredada se convierte en lógica enredada con latencia y reintentos añadidos encima. Limpia primero el código dentro del monolito.

Nanoservicios. Límites tan finos que una sola acción del usuario se abre en abanico a través de una docena de servicios. La sobrecarga de coordinación ahoga cualquier beneficio, y nadie puede sostener el sistema en la cabeza.

Una base de datos compartida detrás de muchos servicios. Si varios servicios leen y escriben en las mismas tablas, están acoplados por completo. Un solo cambio de esquema rompe a todos, y tienes independencia solo de nombre.

Reescribir en lugar de estrangular. Congelar el trabajo de funciones para reconstruirlo todo desde cero. El sistema viejo sigue evolucionando mientras reconstruyes, el objetivo se mueve y el proyecto se desborda. Haz crecer lo nuevo alrededor de lo viejo en su lugar.

La conclusión pragmática

Los microservicios son una herramienta para un conjunto específico de problemas, sobre todo la escala organizativa y el despliegue independiente, y resuelven esos problemas cambiando simplicidad por autonomía. Ese cambio vale la pena cuando tienes las personas, la presión y la madurez operativa para absorber el costo. Es un mal cambio cuando persigues una tendencia o tapas código desordenado. Empieza modular, sé honesto sobre tus señales, migra con la higuera estranguladora y traza tus límites en torno a los dominios en lugar de las capas.

Si estás sopesando esta decisión y quieres una segunda opinión lúcida, ese es exactamente el tipo de problema que ayudamos a los equipos a resolver. Explora nuestros servicios de ingeniería de software y nube, o ponte en contacto para conversar sobre tu arquitectura con el equipo de Innovation T.

#microservicios#arquitectura#monolito#escalabilidad

¿Listo para construir con Innovation T?

Ya se trate de seguridad, crecimiento o ingeniería, nuestro equipo puede ayudarte a lograrlo con calidad.