Una vez que hemos descompuesto la aplicación en servicios autónomos, surge una pregunta inevitable: ¿cómo se comunican entre sí y cómo los encuentra el mundo exterior? La comunicación es el sistema nervioso de una arquitectura distribuida, y elegir mal el estilo (síncrono frente a asíncrono) o la infraestructura (gateway, descubrimiento de servicios) puede convertir un conjunto de servicios elegantes en un frágil monolito distribuido.
En esta lección compararemos la comunicación síncrona (REST, gRPC) con la asíncrona (mensajería, eventos), entenderemos el papel del API Gateway como puerta de entrada y veremos cómo el service discovery permite que los servicios se localicen en un entorno dinámico.
Contenido
- Síncrono frente a asíncrono
- Comunicación síncrona: REST y gRPC
- Comunicación asíncrona: mensajería y eventos
- API Gateway
- Service Discovery
- Eligiendo el estilo adecuado
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Síncrono frente a asíncrono
La primera gran decisión es si el llamante espera la respuesta (síncrono) o continúa sin esperar (asíncrono).
| Aspecto | Síncrono (REST/gRPC) | Asíncrono (mensajes/eventos) |
|---|---|---|
| Modelo | Petición/respuesta | Publicar/suscribir o cola |
| Acoplamiento temporal | Alto: ambos deben estar vivos | Bajo: desacoplados en el tiemp |
| Simplicidad | Mayor (flujo directo) | Menor (más piezas) |
| Disponibilidad | Si B cae, A se ve afectado | A sigue aunque B esté caído |
| Consistencia | Inmediata | Eventual |
| Casos típicos | Consultas, validaciones inmediatas | Notificaciones, integración, eventos |
La idea clave del acoplamiento temporal: en una llamada síncrona, el servicio destino debe estar disponible en ese instante. En la asíncrona, el mensaje espera en una cola hasta que el destino pueda procesarlo.
- Comunicación síncrona: REST y gRPC
REST sobre HTTP es el estándar de facto: sencillo, universal, basado en recursos y verbos HTTP.
// Cliente REST con timeout explícito (nunca confíes en la red)
public Cliente obtenerCliente(String id) {
return webClient.get()
.uri("/clientes/{id}", id)
.retrieve()
.bodyToMono(Cliente.class)
.timeout(Duration.ofSeconds(2)) // corta si tarda demasiado
.block();
}El timeout es imprescindible: sin él, un servicio lento bloquearía indefinidamente al llamante (recuerda la falacia "la latencia es cero").
gRPC usa HTTP/2 y serialización binaria (Protocol Buffers), lo que lo hace más rápido y compacto, ideal para comunicación interna de alto rendimiento.
// Definición del contrato en Protocol Buffers (clientes.proto)
syntax = "proto3";
service ClienteService {
rpc ObtenerCliente (ClienteRequest) returns (ClienteResponse);
}
message ClienteRequest { string id = 1; }
message ClienteResponse { string id = 1; string nombre = 2; }A partir de este contrato, gRPC genera automáticamente el código de cliente y servidor. La comparación rápida:
| Criterio | REST/JSON | gRPC/Protobuf |
|---|---|---|
| Formato | Texto (JSON) | Binario |
| Rendimiento | Bueno | Excelente |
| Legibilidad | Alta | Baja (binario) |
| Navegadores | Soporte nativo | Limitado |
| Streaming | Limitado | Nativo (bidireccional) |
| Uso típico | APIs públicas | Comunicación interna |
- Comunicación asíncrona: mensajería y eventos
En la comunicación asíncrona, un servicio publica un mensaje en un broker (RabbitMQ, Apache Kafka, etc.) y otros lo consumen cuando pueden. Distinguimos:
- Comandos: petición para que algo ocurra ("EnviarRecibo"). Suelen tener un único destinatario.
- Eventos: notificación de algo que ya ocurrió ("PolizaCreada"). Pueden tener muchos suscriptores.
{
"tipo": "PolizaCreada",
"version": 1,
"ocurridoEn": "2026-06-30T10:15:00Z",
"datos": {
"polizaId": "POL-00123",
"ramo": "hogar"
}
}Este evento describe un hecho consumado. El servicio de facturación puede suscribirse para generar el primer recibo, y el de notificaciones para enviar un correo de bienvenida, sin que el servicio de pólizas sepa nada de ellos. Esto reduce drásticamente el acoplamiento.
// Consumidor de eventos: reacciona a PolizaCreada
@KafkaListener(topics = "polizas")
public void onPolizaCreada(PolizaCreadaEvent evento) {
// Procesamiento idempotente: si ya lo procesamos, salimos
if (yaProcesado(evento.getId())) return;
facturacion.generarPrimerRecibo(evento.getPolizaId());
marcarProcesado(evento.getId());
}La idempotencia es crítica porque los brokers suelen garantizar entrega "al menos una vez", lo que significa que un mensaje puede llegar duplicado.
- API Gateway
El API Gateway es la única puerta de entrada para los clientes externos. En lugar de que el frontend conozca la dirección de cada microservicio, habla solo con el gateway, que enruta y agrega.
graph LR
APP[App Móvil] --> GW[API Gateway]
WEB[Web] --> GW
GW --> S1[Servicio Pólizas]
GW --> S2[Servicio Clientes]
GW --> S3[Servicio Siniestros]Responsabilidades habituales del gateway:
| Función | Descripción |
|---|---|
| Enrutamiento | Dirige cada petición al servicio correcto. |
| Autenticación | Valida tokens antes de pasar al backend. |
| Limitación de tasa | Protege frente a abusos (rate limiting). |
| Agregación | Combina respuestas de varios servicios en una. |
| TLS | Termina el cifrado en un único punto. |
# Configuración de rutas en un API Gateway (estilo Spring Cloud Gateway)
spring:
cloud:
gateway:
routes:
- id: polizas
uri: lb://servicio-polizas # lb = balanceo vía service discovery
predicates:
- Path=/api/polizas/**
- id: clientes
uri: lb://servicio-clientes
predicates:
- Path=/api/clientes/**Cada ruta asocia un patrón de URL (predicates) con un servicio destino (uri). El prefijo lb:// indica que la dirección concreta se resuelve mediante el service discovery, no se codifica a mano.
Cuidado: el gateway no debe contener lógica de negocio. Si lo conviertes en un cerebro central, vuelves al monolito.
- Service Discovery
En la nube, las instancias de un servicio aparecen y desaparecen continuamente (autoescalado, despliegues, fallos). Sus direcciones IP no son fijas. El service discovery resuelve el problema: un registro central donde los servicios se inscriben y se buscan por nombre.
sequenceDiagram
participant S as Servicio Pólizas
participant R as Registro (Discovery)
participant C as Servicio Cliente
S->>R: Me registro: polizas -> 10.0.0.5:8080
C->>R: ¿Dónde está "polizas"?
R->>C: 10.0.0.5:8080
C->>S: Petición HTTPHay dos modelos:
| Modelo | Descripción | Ejemplo |
|---|---|---|
| Del lado del cliente | El cliente consulta el registro y elige instancia. | Eureka + Ribbon |
| Del lado del servidor | Un balanceador resuelve por ti. | Kubernetes Services |
En Kubernetes, el descubrimiento está integrado: cada servicio tiene un nombre DNS estable y el clúster balancea automáticamente.
# En Kubernetes, "servicio-polizas" es un nombre DNS resoluble desde otros pods
apiVersion: v1
kind: Service
metadata:
name: servicio-polizas
spec:
selector:
app: polizas
ports:
- port: 8080Con esta definición, cualquier pod puede llamar a http://servicio-polizas:8080 y Kubernetes enruta a una instancia sana, sin que nadie gestione IPs.
- Eligiendo el estilo adecuado
Una guía práctica para decidir:
- ¿Necesitas la respuesta ahora para continuar (validar un dato, mostrar una pantalla)? → Síncrono.
- ¿Estás notificando algo que ya pasó y a varios interesados? → Asíncrono (eventos).
- ¿Quieres que el servicio destino pueda estar caído sin afectarte? → Asíncrono.
- ¿La operación es una consulta simple y de baja latencia? → Síncrono (gRPC interno o REST).
El principio general: prefiere lo asíncrono para reducir acoplamiento, y reserva lo síncrono para consultas que realmente necesitan respuesta inmediata.
Errores Comunes y Consejos
- Llamadas síncronas en cadena: A llama a B, que llama a C, que llama a D. La latencia y la probabilidad de fallo se acumulan. Considera eventos.
- Olvidar los timeouts: toda llamada síncrona debe tener timeout. Sin él, un servicio lento te arrastra.
- Meter lógica de negocio en el gateway: el gateway enruta y protege, no decide. La lógica vive en los servicios.
- Asumir entrega exactamente una vez: los brokers entregan "al menos una vez". Haz los consumidores idempotentes.
- Codificar IPs a mano: usa service discovery; las direcciones cambian constantemente.
Ejercicios
- Compara en una tabla la comunicación síncrona y la asíncrona en términos de acoplamiento temporal, disponibilidad y consistencia.
- Explica por qué un consumidor de eventos debe ser idempotente y muestra en pseudocódigo Java cómo lograrlo.
- Tienes una pantalla que necesita mostrar, en una sola llamada, los datos del cliente y sus pólizas, que viven en servicios distintos. ¿Qué pieza de la arquitectura usarías y cómo?
Soluciones
-
Síncrono: acoplamiento temporal alto (ambos deben estar vivos), disponibilidad dependiente del destino, consistencia inmediata. Asíncrono: acoplamiento temporal bajo (el mensaje espera en cola), disponibilidad independiente, consistencia eventual.
-
Debe ser idempotente porque los brokers entregan "al menos una vez", así que un mensaje puede llegar duplicado. Solución:
public void onEvento(Evento e) {
if (yaProcesado(e.getId())) return; // descarta duplicados
procesar(e);
marcarProcesado(e.getId());
}- El API Gateway con un patrón de agregación (o un BFF, Backend For Frontend): el gateway recibe la petición, llama en paralelo al servicio de clientes y al de pólizas, y combina ambas respuestas en un único JSON que devuelve a la pantalla. Así el frontend hace una sola llamada.
Conclusión
Hemos aprendido que la comunicación entre servicios se mueve entre dos polos: la síncrona (REST, gRPC), simple pero acoplada en el tiempo, y la asíncrona (eventos, mensajes), desacoplada pero eventualmente consistente. El API Gateway ofrece una puerta de entrada única que enruta, protege y agrega, mientras que el service discovery permite que los servicios se encuentren en un entorno dinámico.
Toda esta comunicación por red comparte un riesgo: las dependencias fallan. La siguiente lección, Patrones de Resiliencia: Circuit Breaker, Retry y Bulkhead, nos enseñará a diseñar servicios que sobreviven a los fallos parciales en lugar de propagarlos.
Curso de Arquitectura de Aplicaciones
Módulo 1: Fundamentos de la Arquitectura de Aplicaciones
- ¿Qué es la Arquitectura de Aplicaciones?
- El Rol del Arquitecto de Software
- Atributos de Calidad y Requisitos No Funcionales
- Decisiones Arquitectónicas y Compromisos (Trade-offs)
- Documentación de Arquitectura: Vistas y el Modelo C4
Módulo 2: Principios y Tácticas de Diseño
- Acoplamiento, Cohesión y Separación de Responsabilidades
- Principios SOLID Aplicados a la Arquitectura
- DRY, KISS, YAGNI y Otros Principios de Diseño
- Tácticas Arquitectónicas para los Atributos de Calidad
- Gestión de la Deuda Técnica
Módulo 3: Estilos y Patrones Arquitectónicos
- Arquitectura Monolítica
- Arquitectura en Capas (N-Tier)
- Arquitectura Cliente-Servidor
- Arquitectura Hexagonal (Puertos y Adaptadores)
- Arquitectura Limpia y Cebolla (Clean & Onion)
Módulo 4: Arquitecturas Distribuidas y Microservicios
- Introducción a los Sistemas Distribuidos
- Arquitectura de Microservicios
- Descomposición de Servicios y Bounded Contexts
- API Gateway, Service Discovery y Comunicación entre Servicios
- Patrones de Resiliencia: Circuit Breaker, Retry y Bulkhead
- El Teorema CAP y la Consistencia de Datos
Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería
- Fundamentos de la Arquitectura Orientada a Eventos
- Mensajería Asíncrona: Colas y Brokers
- Patrones de Eventos: Event Sourcing y CQRS
- Gestión de Transacciones Distribuidas: Patrón Saga
- Streaming de Datos en Tiempo Real
Módulo 6: Diseño Dirigido por el Dominio (DDD)
- Conceptos Fundamentales del DDD
- Diseño Estratégico: Bounded Contexts y Lenguaje Ubicuo
- Diseño Táctico: Entidades, Agregados y Repositorios
- Mapeo de Contextos (Context Mapping)
Módulo 7: Datos y Persistencia
- Estrategias de Persistencia: SQL vs NoSQL
- Patrones de Acceso a Datos: Repository, Unit of Work y DAO
- Base de Datos por Servicio y Gestión de Datos Distribuidos
- Caché y Estrategias de Invalidación
Módulo 8: Arquitectura en la Nube y Despliegue
- Fundamentos del Cloud Computing (IaaS, PaaS, SaaS)
- Contenedores y Orquestación con Docker y Kubernetes
- Arquitectura Serverless
- Patrones de Diseño Cloud-Native
- Infraestructura como Código (IaC)
Módulo 9: Calidad, Seguridad y Observabilidad
- Escalabilidad: Horizontal vs Vertical y Balanceo de Carga
- Alta Disponibilidad y Tolerancia a Fallos
- Seguridad por Diseño y Autenticación/Autorización
- Observabilidad: Logging, Métricas y Trazabilidad
- Rendimiento y Pruebas de Carga
