Saltar al contenido principal

Microservicios

Una arquitectura de microservicios divide la aplicación en servicios pequeños, desplegables por separado y comunicados por red.

Es una decisión con consecuencias enormes y difíciles de revertir, así que este capítulo empieza por la parte que casi nunca se cuenta: por qué probablemente no los necesitamos.

Qué problema resuelven de verdad​

La respuesta habitual es "escalabilidad". Suele ser falsa: un monolito bien hecho escala horizontalmente sin problema, y muchas aplicaciones grandes funcionan así.

El problema que resuelven de verdad es organizativo. Cuando tenemos ochenta desarrolladores tocando el mismo repositorio, coordinar releases se convierte en el cuello de botella. Los microservicios permiten que quince equipos desplieguen sin pedirse permiso.

Es la ley de Conway aplicada a propósito: la arquitectura acaba pareciéndose a la estructura de nuestra organización, así que más vale elegir la estructura antes.

Si tenemos menos de veinte desarrolladores, es muy probable que no tengamos ese problema.

Lo que cuestan​

Con un monolitoCon microservicios
Una llamada a funciónUna llamada de red que puede fallar
Una transacción de base de datosConsistencia eventual y compensaciones
Un despliegueDocenas, coordinados
Un logTrazas distribuidas o no te enteras de nada
Refactorizar entre módulosCambiar un contrato entre equipos

Fijémonos en la segunda fila, que es la que más duele. En un monolito, "cobrar y reservar stock" es una transacción: o pasan las dos cosas o ninguna. Repartido en dos servicios, eso deja de existir y tenemos que construirlo a mano.

Y en la cuarta: sin trazas distribuidas, depurar una petición que atraviesa seis servicios es imposible. La observabilidad deja de ser recomendable y pasa a ser un requisito.

Cuándo NO usarlos​

Señales bastante fiables de que nos vamos a arrepentir:

  • El equipo es pequeño. Menos de quince o veinte personas: el coste de coordinación que resuelven no lo tenemos.
  • No tenemos observabilidad. Sin trazas y logs centralizados, cada incidente será una investigación a ciegas.
  • No tenemos CI/CD sólido. Si desplegar un servicio es manual, desplegar veinte es inviable.
  • El dominio no está claro. Si aún no sabemos dónde están las fronteras del negocio, las vamos a poner mal — y mover una frontera entre servicios cuesta muchísimo más que mover un módulo.
  • Lo hacemos porque lo hace Netflix. Netflix tiene problemas que nosotros no tenemos, y equipos de plataforma que tampoco.

La alternativa razonable: el monolito modular​

Un monolito con fronteras internas bien definidas nos da casi todas las ventajas organizativas sin la complejidad operativa:

mi-app/
├── facturacion/ ← módulos con fronteras claras
├── inventario/ ← se comunican por interfaces explícitas
└── usuarios/ ← comparten proceso y base de datos

Y tiene una propiedad valiosa: si un día de verdad necesitamos separar, ya sabemos por dónde cortar. Es mucho más fácil extraer un servicio de un monolito modular que arreglar unos microservicios mal divididos.

Empecemos aquí. Separemos cuando el dolor sea real y podamos señalarlo con el dedo.

Cómo dividir​

Si decidimos seguir adelante, la división correcta es por dominio de negocio, no por capa técnica:

❌ Por capa ✅ Por dominio
servicio-de-frontend pedidos
servicio-de-backend pagos
servicio-de-base-de-datos inventario

La prueba práctica: si un cambio de negocio típico obliga a tocar tres servicios y coordinar tres despliegues, la división está mal. Cada servicio debería poder cambiar y desplegarse solo.

Los patrones que vamos a necesitar​

Base de datos por servicio​

Cada servicio es dueño de sus datos, y nadie más los toca directamente.

Es incómodo — se acabaron los JOIN entre dominios — pero es lo que hace posible desplegar por separado. Si dos servicios comparten tablas, están acoplados por la base de datos y no ganas nada.

Circuit breaker​

Sin él, un servicio lento tumba a todos los que dependen de él: las peticiones se acumulan esperando y agotan los hilos disponibles. Es el fallo en cascada.

El patrón corta la comunicación cuando detecta fallos repetidos:

CERRADO ──(muchos fallos)──> ABIERTO ──(pasa un tiempo)──> SEMIABIERTO
↑ │
└──────────────────(la prueba funciona)──────────────────────┘

Abierto, falla de inmediato en lugar de esperar. Eso protege al que llama y da respiro al que está caído.

Va siempre acompañado de timeouts agresivos (una llamada sin timeout es una bomba de relojería) y reintentos con espera creciente y aleatoria — sin la parte aleatoria, todos los clientes reintentan a la vez y rematan al servicio que intentaba recuperarse.

Saga​

Sustituye a la transacción distribuida, que en la práctica no existe. Una secuencia de pasos locales, cada uno con su compensación:

Reservar stock → Cobrar → Enviar
↓ ↓
liberar stock devolver

Si falla el envío, se ejecutan las compensaciones hacia atrás. Con una diferencia importante respecto a una transacción: no es un rollback. El cobro ocurrió y la devolución también; ambos quedan en el histórico. El sistema es correcto, pero el modelo de negocio tiene que aceptarlo.

API Gateway​

Un único punto de entrada para los clientes, que se encarga del enrutado, la autenticación y los límites de peticiones. Evita que cada cliente tenga que conocer la topología interna, y que cada servicio implemente la autenticación por su cuenta.

Comunicación​

Síncrona (HTTP, gRPC) — simple y directa, pero acopla: si el que responde está caído, el que llama falla. Úsala cuando necesites la respuesta para continuar.

Asíncrona (colas, eventos) — el emisor publica y sigue. Desacopla de verdad y absorbe picos, a cambio de consistencia eventual y de que depurar sea más difícil. Úsala para todo lo que pueda esperar.

La regla práctica: síncrono solo cuando de verdad necesitamos la respuesta ahora. Un pedido no necesita esperar a que se envíe el email de confirmación.

Lo que necesitamos antes de empezar​

Una lista honesta de requisitos previos:

  • ✅ CI/CD automatizado, por servicio
  • ✅ Contenedores y algún tipo de orquestación
  • ✅ Trazas distribuidas y logs centralizados
  • ✅ Un catálogo de servicios y sus dueños
  • ✅ Contratos de API versionados y con compatibilidad hacia atrás
  • ✅ Entorno de desarrollo local que no obligue a levantar veinte servicios

Si nos faltan tres o más, resolvámoslos antes. Son más baratos de montar con un monolito que en medio de una migración.

Resumen​

  • Los microservicios resuelven un problema organizativo, no técnico
  • Con menos de veinte desarrolladores, probablemente no tenemos ese problema
  • Lo que más duele es perder la transacción de base de datos
  • El monolito modular da las ventajas sin la complejidad, y te enseña por dónde cortar
  • Divide por dominio de negocio, nunca por capa técnica
  • Timeouts, circuit breakers y reintentos con aleatoriedad no son opcionales
  • Asíncrono por defecto; síncrono solo si necesitamos la respuesta ya
  • Sin observabilidad, no empieces

Recursos adicionales​


💡 Recuerda: los microservicios cambian complejidad de código por complejidad de operación. Asegurémonos de que ese cambio nos compensa.

En el próximo capítulo cerramos el curso con la disciplina que mide si todo esto funciona.