main content
< Volver a blog sobre aplicaciones móviles

Microservicios en aplicaciones web: guía de adopción

Arquitectura de microservicios para aplicaciones web a medida: cuándo adoptarla y cómo implementarla paso a paso

Monolito frente a microservicios: un análisis sin dogmatismos

Durante la última década, la conversación sobre arquitectura ha pendulado entre extremos. Primero los microservicios se vendieron como la cura universal para cualquier problema de escalabilidad. Después llegó la resaca: artículos advirtiendo de complejidad innecesaria y proyectos que se ahogaban en su propia infraestructura. La respuesta honesta, como casi siempre en software, depende del contexto.

Un monolito bien estructurado se despliega como una única unidad: un proceso, una base de datos compartida y un único código base. Sus ventajas son tangibles. Despliegas un artefacto a través de un pipeline. Depuras con un solo debugger. Las transacciones ACID funcionan de fábrica. Y las llamadas entre componentes no atraviesan la red, así que la latencia interna es despreciable. Para una aplicación web a medida con un equipo de entre 3 y 8 desarrolladores, un monolito modular suele ser la opción más productiva durante los primeros 18-24 meses.

Los microservicios reparten la aplicación en servicios independientes, cada uno con su proceso, su base de datos y su propio ciclo de despliegue. Sus ventajas afloran cuando la organización crece: equipos autónomos que despliegan sin pedir turno, escalado independiente de los componentes más demandados, libertad para elegir la tecnología más adecuada para cada problema y aislamiento de fallos (un servicio caído no tumba toda la plataforma). El precio que se paga es real: complejidad operativa, latencia de red, consistencia eventual, trazabilidad distribuida y una factura de infraestructura sensiblemente mayor.

La pregunta útil no es "¿monolito o microservicios?", sino "¿en qué punto de madurez está mi producto, mi equipo y mi organización?".

Cuándo los microservicios aportan valor real

Hay señales bastante nítidas para saber cuándo una aplicación web a medida empieza a pedir microservicios. La primera es organizativa. Si más de tres equipos comparten el mismo código base, los conflictos de merge, las dependencias cruzadas y la coordinación de releases acaban convirtiéndose en un cuello de botella diario. La ley de Conway sostiene que los sistemas reflejan la estructura de comunicación de la organización; los microservicios permiten alinear la arquitectura con equipos autónomos en lugar de luchar contra ella.

La segunda señal es técnica: módulos con requisitos de escalado radicalmente distintos. Si la búsqueda necesita 20 instancias para absorber el tráfico mientras que el panel de administración funciona con una sola, escalar el monolito entero es un desperdicio. Hablamos de miles de euros mensuales tirados en infraestructura cloud que nadie aprovecha.

La tercera tiene que ver con el ciclo de vida. ¿Qué pasa cuando la velocidad de cambio varía drásticamente entre módulos? El catálogo de productos puede pedir despliegues diarios para ajustar precios y promociones, mientras que el módulo de facturación solo cambia trimestralmente por requisitos regulatorios. Forzar ambos ritmos en un único pipeline frena al rápido y mete riesgo gratuito en el estable.

Si ninguna de estas señales aparece, adoptar microservicios es añadir complejidad a cambio de nada. Un monolito modular con separación clara de módulos, interfaces bien definidas y tests de contrato es una base sólida que permite migrar a microservicios más adelante, cuando las circunstancias lo justifiquen.

Domain-Driven Design: la base para definir los límites de cada servicio

El error más habitual al diseñar microservicios es trocear por capas técnicas: un servicio para la API, otro para la lógica de negocio, otro para la persistencia. Esa división genera servicios fuertemente acoplados que no pueden desplegarse ni evolucionar de forma independiente. La alternativa es Domain-Driven Design (DDD), que identifica bounded contexts, es decir, las fronteras naturales del negocio.

Un bounded context agrupa conceptos que comparten un mismo modelo de dominio y un mismo lenguaje ubicuo. En comercio electrónico, los típicos serían: catálogo (productos, categorías, atributos), carrito (líneas de pedido, descuentos, cálculo de totales), pagos (transacciones, pasarelas, conciliación), envíos (seguimiento, transportistas, almacenes) y usuarios (registro, autenticación, perfiles).

Cada bounded context es un candidato natural a microservicio. La clave está en la cohesión: dentro de un contexto los conceptos están fuertemente unidos, y entre contextos la comunicación se reduce al mínimo a través de interfaces explícitas. Un producto en el contexto de catálogo arrastra atributos detallados (descripción, imágenes, SEO); en el contexto de carrito, ese mismo producto colapsa a un identificador, un nombre y un precio. Esa diferencia de modelos no es un defecto, es deseable: cada servicio gestiona exactamente la información que necesita.

Para identificar bounded contexts en una aplicación existente, las sesiones de Event Storming funcionan sorprendentemente bien. En un taller de 3-4 horas con desarrolladores y expertos del negocio, se mapean los eventos de dominio (PedidoCreado, PagoConfirmado, EnvíoExpedido) y se agrupan en clusters que revelan las fronteras naturales del sistema.

Patrones de comunicación: síncrona frente a asíncrona

La comunicación entre microservicios es lo que más impacto tiene en rendimiento, fiabilidad y complejidad. Hay dos familias de patrones, y la mayoría de aplicaciones reales usan las dos a la vez.

La comunicación síncrona (REST sobre HTTP o gRPC) es la más intuitiva: el servicio A envía una petición al servicio B y espera la respuesta. REST sigue siendo el estándar de facto, con soporte amplio en cualquier lenguaje y framework. gRPC, apoyado en Protocol Buffers y HTTP/2, ofrece mejor rendimiento (serialización binaria entre 3x y 10x más rápida que JSON), tipado fuerte con contratos .proto compartidos y streaming bidireccional nativo. Para comunicaciones internas con alta frecuencia de llamadas, gRPC es la opción más eficiente.

El riesgo de las llamadas síncronas es la propagación de fallos en cascada. Si pagos depende síncronamente de fraude, fraude de scoring, y scoring de datos de cliente, un timeout en el último puede tirar toda la cadena. Patrones como circuit breaker (cortar la comunicación con un servicio que falla repetidamente), retry con backoff exponencial (reintentos con intervalos crecientes) y bulkhead (aislar pools de conexiones por servicio) reducen el riesgo, pero no lo eliminan.

La comunicación asíncrona a través de un broker (RabbitMQ, Apache Kafka, Amazon SQS) desacopla los servicios en el tiempo. El servicio A publica un evento ("PedidoCreado") en un topic y sigue su camino sin esperar a nadie. Los suscriptores lo procesan a su ritmo. Encaja como un guante para operaciones que no necesitan respuesta inmediata: enviar el email de confirmación, actualizar el índice de búsqueda, generar la factura en PDF. Kafka brilla cuando hace falta persistencia de eventos, alto throughput (millones de mensajes por segundo) y capacidad de reprocesar el histórico. RabbitMQ es más sencillo de operar y se basta para volúmenes moderados (miles de mensajes por segundo).

API Gateway: el punto de entrada unificado

En una arquitectura de microservicios, ni el navegador ni la app móvil deberían hablar directamente con cada servicio. Un API Gateway actúa como puerta única que enruta las peticiones al servicio correspondiente y concentra funciones transversales: autenticación y autorización (validar tokens JWT), rate limiting (proteger contra abuso), transformación de respuestas (componer una respuesta a partir de varios servicios), caché de respuestas frecuentes y terminación SSL.

Las opciones más habituales son Kong (open source, extensible con plugins en Lua), Envoy (proxy de alto rendimiento, base de service meshes como Istio), AWS API Gateway (gestionado, integrado con el ecosistema AWS) y Traefik (configuración automática mediante etiquetas Docker/Kubernetes). Si la aplicación tiene un BFF (Backend For Frontend) diferenciado para web y móvil, el gateway permite mantener contratos específicos para cada cliente sin duplicar lógica en los servicios subyacentes.

Un error frecuente es convertir el API Gateway en otro servicio cargado de lógica de negocio. El gateway debe limitarse a enrutamiento y funciones transversales. Si empiezas a escribir transformaciones complejas o reglas de negocio dentro de él, falta un servicio de orquestación o los límites entre servicios no están bien trazados.

Gestión de datos: una base de datos por servicio

El principio rector de la gestión de datos en microservicios es que cada servicio posee y gestiona su propia base de datos. Catálogo puede usar PostgreSQL para consultas complejas, sesiones puede tirar de Redis por velocidad y búsqueda puede vivir en un índice de Elasticsearch. Ningún servicio toca la base de datos de otro: toda comunicación pasa por APIs o eventos.

Esa autonomía tiene un coste: las transacciones distribuidas. En un monolito, crear un pedido, decrementar el stock y registrar el pago caben dentro de una misma transacción ACID. En microservicios cada operación vive en un servicio distinto con su propia base de datos. El patrón Saga coordina esa secuencia mediante transacciones locales encadenadas. Si una falla, se ejecutan transacciones compensatorias para revertir lo anterior. Hay dos variantes: orquestación (un servicio central dirige la saga) y coreografía (cada servicio reacciona a eventos y decide el siguiente paso). La orquestación es más fácil de entender y depurar; la coreografía ofrece menor acoplamiento a cambio de complicar el rastreo del flujo completo.

El patrón CQRS (Command Query Responsibility Segregation) complementa las sagas separando los modelos de lectura y escritura. El servicio de pedidos escribe en su base transaccional, y un consumidor de eventos proyecta una vista desnormalizada optimizada para consultas en otra base. Así puedes escalar las lecturas con independencia de las escrituras y resolver consultas que combinan datos de varios servicios sin pelear con joins distribuidos.

CI/CD para microservicios: pipelines independientes

Una de las ventajas centrales de los microservicios es poder desplegar cada uno por separado. Esto exige que cada servicio tenga su propio pipeline de CI/CD con las siguientes fases: ejecución de tests unitarios y de integración, análisis estático de código y vulnerabilidades, construcción de la imagen Docker, push a un registro de contenedores (Harbor, Docker Hub, AWS ECR), despliegue en staging, ejecución de tests de contrato (para verificar compatibilidad con los servicios consumidores) y despliegue en producción con estrategia canary o blue-green.

Los tests de contrato son críticos y merecen una mención aparte. Cuando carrito consume la API de catálogo, existe un contrato implícito: el endpoint GET /products/{id} devuelve un JSON con campos id, name y price. Si el equipo de catálogo renombra price a unit_price, sus tests unitarios siguen verdes, pero carrito explota en producción. Herramientas como Pact permiten que el consumidor (carrito) defina sus expectativas sobre el proveedor (catálogo) como tests automatizados que corren en el pipeline del proveedor antes de cada despliegue.

El código puede vivir como monorepo (todos los servicios en un único repositorio) o como multirepo (un repositorio por servicio). El monorepo facilita refactors transversales y reutilización de código, pero requiere herramientas serias (Bazel, Nx) para gestionar builds selectivos. El multirepo aísla completamente, pero complica los cambios que afectan a varios servicios a la vez. Para equipos de menos de 30 personas, el monorepo con builds incrementales suele rendir mejor.

Contenedores y orquestación con Docker y Kubernetes

Docker es el estándar para empaquetar microservicios en contenedores portables e inmutables. Cada servicio se describe con un Dockerfile que define imagen base, dependencias y punto de entrada. Las imágenes multi-stage permiten compilar el servicio en una etapa y ejecutarlo en otra mínima (distroless o Alpine), lo que reduce la superficie de ataque y baja el tamaño de la imagen de 800 MB a menos de 50 MB en muchos casos.

Kubernetes se ha asentado como la plataforma de orquestación en producción. Gestiona despliegue, escalado automático (HPA basado en CPU, memoria o métricas custom), auto-reparación (reinicio de contenedores caídos), service discovery (cada servicio accesible por nombre DNS interno), balanceo de carga y gestión de configuración y secretos. Para aplicaciones con menos de 10 servicios, un clúster gestionado (EKS, GKE, AKS) con dos o tres nodos basta y evita tener que operar el plano de control.

Helm simplifica los despliegues en Kubernetes mediante charts que encapsulan los recursos necesarios (Deployment, Service, Ingress, ConfigMap, HPA) en plantillas parametrizables. Un chart bien diseñado despliega el mismo servicio en desarrollo, staging y producción cambiando solo los valores de configuración. Si tu organización está valorando el salto a microservicios y necesita aterrizar la estrategia técnica a vuestro contexto, contacta con nuestro equipo para una sesión de diagnóstico arquitectónico.

Monitorización de sistemas distribuidos

Un sistema de microservicios sin observabilidad es un avión sin instrumentos. La complejidad propia de las comunicaciones entre servicios exige, como mínimo, tres capacidades: métricas por servicio (latencia, tasa de errores, throughput), logs centralizados con correlación por trace ID y trazas distribuidas que muestren el recorrido completo de cada petición.

El patrón de service mesh (Istio, Linkerd) añade una capa de proxy sidecar junto a cada contenedor. Captura automáticamente métricas de red (latencia entre servicios, tasa de errores, reintentos) sin instrumentar el código. Linkerd es más ligero y fácil de operar; Istio ofrece más funcionalidades (políticas de seguridad, traffic shifting, fault injection) a cambio de consumir más recursos y una curva de aprendizaje más pronunciada. Para equipos que arrancan con microservicios, Linkerd es una elección pragmática que aporta observabilidad instantánea con un overhead inferior al 2% en latencia.

Los health checks completan el cuadro. Cada servicio debería exponer al menos dos endpoints: /health/live (el proceso está vivo, usado por Kubernetes para liveness probes) y /health/ready (el servicio puede atender tráfico, incluida la conectividad con sus dependencias). Un servicio que responde a liveness pero no a readiness se mantiene vivo pero queda fuera del balanceador hasta que se recupere, evitando que reciba tráfico que no puede procesar.

Anti-patrones que comprometen la arquitectura

La experiencia acumulada en proyectos de microservicios ha dejado un catálogo de errores recurrentes que conviene esquivar desde el diseño inicial.

El monolito distribuido es el anti-patrón más destructivo: servicios que comparten base de datos, que se despliegan siempre juntos y que no pueden funcionar unos sin otros. Acumula toda la complejidad de los microservicios sin ninguna de sus ventajas. Si cada cambio te obliga a coordinar despliegues de tres o más servicios, no tienes microservicios: tienes un monolito con latencia de red añadida.

Los nano-servicios trocean la aplicación en piezas demasiado pequeñas y generan una explosión de artefactos imposible de gestionar. Un servicio con una sola entidad y dos endpoints probablemente debería ser un módulo dentro de un servicio más amplio. Una regla práctica útil: un servicio debe poder ser desarrollado y operado por un equipo de entre 3 y 8 personas.

El acoplamiento por datos compartidos aparece cuando varios servicios leen de la misma tabla. Cualquier cambio en el esquema obliga a coordinar a todos los equipos consumidores. La solución es que cada servicio mantenga su propia copia de los datos que necesita, sincronizada mediante eventos.

La orquestación excesiva convierte un servicio central en un punto único de fallo que termina conociendo los detalles de implementación de todos los demás. El equilibrio razonable combina orquestación (para flujos de negocio que exigen coordinación explícita) con coreografía (para reacciones a eventos que no requieren un director central).

Hoja de ruta para una transición ordenada

Migrar un monolito a microservicios no debería plantearse como una reescritura completa, un enfoque que fracasa en más del 70% de los casos según la experiencia documentada en la industria. El patrón Strangler Fig, inspirado en las higueras que crecen alrededor de un árbol hasta reemplazarlo, propone una migración incremental.

El primer paso es elegir el bounded context que más se beneficiará de la independencia: el que cambia con más frecuencia, el que tiene requisitos de escalado propios o el que un equipo concreto puede asumir como suyo. Se extrae ese contexto como servicio independiente, con su base de datos, su pipeline de CI/CD y una API bien definida. El monolito se modifica para llamar al nuevo servicio en lugar de ejecutar esa lógica internamente. Un proxy o un API Gateway enruta el tráfico al servicio nuevo mientras el código antiguo se mantiene como fallback durante el periodo de validación.

Se repite el proceso, un contexto cada vez, priorizando por valor de negocio y riesgo técnico. Los contextos con menos dependencias se extraen primero porque la migración es más sencilla. Los contextos con dependencias cruzadas se dejan para el final, cuando la infraestructura de comunicación (broker de eventos, service mesh, observabilidad) ya está madura. Una migración realista de un monolito de tamaño medio (entre 50.000 y 200.000 líneas de código) a microservicios lleva entre 12 y 24 meses, ejecutada por un equipo dedicado que destina al menos el 40% de su capacidad al esfuerzo de migración mientras sostiene la operativa del monolito vigente.

La arquitectura de microservicios no es un destino, sino una herramienta que se adopta cuando la complejidad organizativa y técnica lo justifica. El mejor sistema es el más simple que resuelve el problema actual y deja la puerta abierta a evolucionar cuando las circunstancias cambien.

Contacta con nosotros
Fila 1