Patrones de Comunicación entre Microservicios
Elige entre patrones de comunicación síncronos y asíncronos para arquitecturas de microservicios resilientes.
Visión General
Los microservicios deben intercambiar datos para cumplir con las solicitudes de los usuarios, pero elegir el estilo de comunicación equivocado puede convertir un sistema distribuido en una red frágil y fuertemente acoplada. Cada interacción entre servicios es un punto potencial de fallo: picos de latencia, fallos parciales, particiones de red y errores en cascada pueden surgir de una sola dependencia lenta.
Esta receta compara los principales patrones de comunicación utilizados en microservicios productivos: llamadas síncronas REST y gRPC, mensajería asíncrona con brokers de mensajes, y arquitecturas orientadas a eventos. Aprenderás cuándo usar cada uno, cómo hacerlos resilientes con reintentos, timeouts, circuit breakers e idempotencia, y cómo coordinar transacciones de negocio de larga duración con sagas.
Cuándo Usar
Usa este recurso cuando:
- Debas elegir entre comunicación síncrona (REST, gRPC) y asíncrona (messaging, event-driven).
- Diseñes comunicación resiliente con circuit breakers y reintentos.
- Coordines transacciones distribuidas con sagas.
Solución
Llamada REST síncrona
# Python con httpx
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def get_order(order_id: str) -> dict:
with httpx.Client(timeout=5.0) as client:
r = client.get(f"http://orders-service/orders/{order_id}")
r.raise_for_status()
return r.json()
// JavaScript con fetch
async function getOrder(orderId) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const res = await fetch(`http://orders-service/orders/${orderId}`, {
signal: controller.signal,
});
clearTimeout(timeout);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// Java con RestTemplate
import org.springframework.web.client.RestTemplate;
RestTemplate rest = new RestTemplate();
Order order = rest.getForObject(
"http://orders-service/orders/{id}", Order.class, orderId);
Productor de mensajes asíncronos
# Python con RabbitMQ (pika)
import pika, json
def publish_order_created(order: dict):
conn = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq'))
ch = conn.channel()
ch.queue_declare(queue='orders.created')
ch.basic_publish(
exchange='',
routing_key='orders.created',
body=json.dumps(order).encode()
)
conn.close()
// Node.js con Kafka (kafkajs)
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ brokers: ['kafka:9092'] });
const producer = kafka.producer();
async function publishOrderCreated(order) {
await producer.connect();
await producer.send({
topic: 'orders.created',
messages: [{ key: order.id, value: JSON.stringify(order) }],
});
await producer.disconnect();
}
Explicación
La comunicación síncrona es el modelo mental más simple: el servicio A llama al servicio B y espera una respuesta. REST sobre HTTP es la opción predeterminada porque es ubicuo, independiente del lenguaje y fácil de depurar. gRPC es más adecuado cuando importan la baja latencia, los payloads binarios y los contratos fuertemente tipados. El costo de las llamadas síncronas es el acoplamiento temporal: si el servicio downstream es lento o está caído, el llamador también se ve afectado.
La comunicación asíncrona desacopla los servicios introduciendo un broker de mensajes. El productor envía un mensaje y continúa inmediatamente; el consumidor lo procesa a su propio ritmo. Esto mejora la resiliencia y el throughput, pero añade complejidad operativa (clustering del broker, dead-letter queues, ordenamiento de mensajes) y dificulta la depuración porque no hay una única stack trace.
Las arquitecturas orientadas a eventos extienden la mensajería haciendo que los cambios de estado sean observables como eventos de dominio. Los consumidores se suscriben a los eventos relevantes, permitiendo capacidades de negocio débilmente acopladas. Usa este patrón cuando múltiples servicios deban reaccionar al mismo hecho sin conocerse entre sí.
Los patrones de resiliencia son obligatorios en cualquier estilo. Agrega timeouts del lado del cliente para dejar de esperar a un peer lento, reintentos con backoff exponencial y jitter para recuperarte de fallos transitorios, circuit breakers para fallar rápido cuando un servicio está inestable, y claves de idempotencia para hacer que los reintentos sean seguros.
Las sagas reemplazan a las transacciones distribuidas. Una saga es una secuencia de transacciones locales, cada una seguida de un evento o mensaje. Si un paso falla, las acciones compensatorias deshacen los pasos anteriores. Esto mantiene los servicios autónomos mientras se preserva la consistencia de negocio.
Variantes
| Estilo | Protocolo | Mejor Para | Compromisos |
|---|---|---|---|
| REST | HTTP/JSON | Uso general, clientes de navegador, APIs públicas | Mayor latencia, contratos flexibles |
| gRPC | HTTP/2 + Protobuf | Servicio a servicio interno, alto throughput | Requiere tooling, menos legible para humanos |
| Messaging | AMQP, SQS, Kafka | Jobs de fondo, nivelación de carga, desacoplamiento | Overhead del broker, consistencia eventual |
| Event-driven | Kafka, event bus | Múltiples consumidores, auditoría, flujos complejos | Evolución de esquemas de eventos, coordinación de consumidores |
| GraphQL | HTTP | Queries flexibles, clientes móviles | Complejidad del servidor, desafíos de caché |
Lo que funciona
- Prefiere la comunicación asíncrona para operaciones de larga duración o no críticas. Usa mensajería o eventos cuando el llamador no necesita un resultado inmediato.
- Configura timeouts agresivos y presupuestos de reintento pequeños. Una tormenta de reintentos puede amplificar una interrupción parcial. Limita los reintentos a 3 intentos y usa backoff exponencial con jitter.
- Haz que las llamadas a downstream sean idempotentes. Pasa un header
Idempotency-Keypara que las solicitudes duplicadas causadas por reintentos no produzcan efectos secundarios. - Despliega circuit breakers alrededor de cada dependencia externa. Abre el circuito tras un umbral de fallos y degrada gracefulmente en lugar de propagar el error.
- Mantén las compensaciones de sagas simples y reversibles. Cada paso de una saga debe tener una acción compensatoria clara que pueda ejecutarse en segundo plano.
Errores Comunes
- Encadenar llamadas síncronas a través de muchos servicios. Cada salto añade latencia y superficie de fallo; los grafos profundos de llamadas se vuelven frágiles.
- Reintentar sin idempotencia. Reintentar un POST puede crear pedidos, cargos o envíos duplicados.
- Ignorar el ordenamiento de mensajes. Kafka con múltiples particiones puede reordenar mensajes; usa mensajes con clave o idempotencia si el orden importa.
- Compartir una base de datos entre servicios. El acoplamiento directo a la base de datos anula el propósito de los microservicios y bloquea el despliegue independiente.
- Bloquear al llamador con un consumidor lento. Si el consumidor no puede seguir el ritmo, las colas crecen y los productores eventualmente sufren back-pressure o caídas.
Preguntas frecuentes
¿Cuándo debo usar REST en lugar de gRPC?
Usa REST para APIs públicas, clientes de navegador y equipos que valoran payloads legibles. Usa gRPC para llamadas internas de alto throughput y baja latencia donde los contratos Protobuf fuertemente tipados y el streaming son beneficiosos.
¿Cómo evito fallos en cascada en llamadas síncronas?
Combina timeouts, reintentos con backoff exponencial, circuit breakers y bulkheads. Considera también cachear datos de solo lectura localmente para que tu servicio sobreviva a una caída downstream.
¿Cuál es la diferencia entre messaging y arquitectura orientada a eventos?
El messaging es un patrón de transporte: un productor envía un mensaje a uno o más consumidores. La arquitectura orientada a eventos es un estilo de diseño donde los servicios publican hechos sobre cambios de estado y otros servicios se suscriben y reaccionan de forma independiente. El messaging es la tubería; el event-driven es la filosofía.
¿Puedo usar sagas con llamadas síncronas?
Las sagas son más naturales con eventos o mensajes asíncronos porque cada paso se completa antes de que se dispare el siguiente. Puedes orquestar sagas de forma síncrona, pero eso reintroduce acoplamiento y los timeouts deben gestionarse cuidadosamente.
¿Cómo manejo la duplicación de mensajes desde un broker?
Diseña consumidores idempotentes. Almacena los IDs de mensajes procesados en una tabla de deduplicación o usa las configuraciones de productor idempotente del broker cuando estén disponibles.
Recursos Relacionados
Arquitectura de Microservicios — Cuándo Usarla y Cuándo No
Guía práctica de microservicios: beneficios, trade-offs, patrones comunes y cuándo elegirlos sobre monolitos. Cubre estrategias de descomposición y complejidad operativa.
GuideDe Monolito a Microservicios — Estrategias de Migración
Guía práctica para descomponer monolitos: strangler fig, branch by abstraction y extracción incremental que reducen riesgo y preservan la continuidad del negocio.
GuideGuía de Arquitectura de Software
Una guía para diseñar arquitectura de software: monolitos vs microservicios, arquitectura en capas, flujo de datos y criterios de selección de tecnología.
GuideEntrevistas de System Design: Framework, Ejemplos y Práctica
Framework práctico para entrevistas de system design: estructura tu respuesta, los conceptos que evalúan los entrevistadores y dos ejemplos resueltos.
GuideTeorema CAP y Trade-offs de Bases de Datos
Teorema CAP explicado para practicantes: trade-offs CP vs AP, PACELC, consistencia tunable por base de datos y cómo evitar split-brain.
RecipeRetry Backoff: Budgets, Deadlines y Config de Librerías
Implementa estrategias de retry resilientes con exponential backoff, jitter e integración de circuit breaker para recuperación de fallas transitorias.