Skip to content
StackPractices
advanced Por StackPractices

Microservicios Event-Driven

Diseña microservicios event-driven con message brokers, event sourcing, CQRS y patrones de consistencia eventual.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Visión General

Los microservicios event-driven se comunican de forma asíncrona a través de eventos en lugar de llamadas directas a API. Esto desacopla servicios, mejora la resiliencia y permite escalado independiente. Patrones como event sourcing, CQRS, orquestación de sagas y el outbox pattern resuelven desafíos comunes: consistencia de datos, ordenamiento de mensajes, manejo de duplicados y recuperación de fallas.

Cuándo Usar

Usa este recurso cuando:

  • Los servicios necesitan escalar independientemente sin acoplamiento fuerte. Consulta Event-Driven Functions para patrones de mensajería async.
  • Manejas procesos de negocio de larga duración a través de múltiples dominios. Consulta Serverless Orchestration para coordinación de workflows.
  • Aseguras consistencia de datos sin transacciones distribuidas. Consulta Retry Logic para manejar fallas transitorias.
  • Construyes pipelines de notificaciones, auditoría o analytics en tiempo real. Consulta Kafka Event Streaming para procesamiento de eventos de alto throughput.

Solución

Event Sourcing con PostgreSQL (Python)

from dataclasses import dataclass
from typing import List
import json

@dataclass
class Event:
    aggregate_id: str
    event_type: str
    payload: dict
    version: int

class OrderAggregate:
    def __init__(self, order_id: str):
        self.order_id = order_id
        self.events: List[Event] = []
        self.status = "pending"
    
    def apply(self, event: Event):
        if event.event_type == "order_placed":
            self.status = "placed"
        elif event.event_type == "payment_received":
            self.status = "paid"
        self.events.append(event)
    
    def place_order(self, items: List[dict]):
        event = Event(
            aggregate_id=self.order_id,
            event_type="order_placed",
            payload={"items": items},
            version=len(self.events) + 1
        )
        self.apply(event)
        return event

Outbox Pattern (Node.js + Kafka)

// Dentro de la misma transacción de base de datos:
await db.transaction(async (trx) => {
  // 1. Actualizar datos de negocio
  await trx('orders').insert({ id: orderId, status: 'placed' });
  
  // 2. Escribir en tabla outbox (misma transacción)
  await trx('outbox').insert({
    topic: 'orders.events',
    key: orderId,
    payload: JSON.stringify({ event: 'order_placed', orderId, items })
  });
});

// Proceso relay separado hace polling de outbox y publica a Kafka
const pending = await db('outbox').where('sent', false).limit(100);
for (const msg of pending) {
  await kafka.producer.send({
    topic: msg.topic,
    messages: [{ key: msg.key, value: msg.payload }]
  });
  await db('outbox').where('id', msg.id).update({ sent: true });
}

Orquestación de Saga (TypeScript)

interface SagaStep {
  name: string;
  execute: () => Promise<void>;
  compensate: () => Promise<void>;
}

class OrderSaga {
  private steps: SagaStep[] = [
    {
      name: 'reserve_inventory',
      execute: () => inventoryService.reserve(order.items),
      compensate: () => inventoryService.release(order.items)
    },
    {
      name: 'process_payment',
      execute: () => paymentService.charge(order.total),
      compensate: () => paymentService.refund(order.total)
    },
    {
      name: 'ship_order',
      execute: () => shippingService.createLabel(order),
      compensate: () => shippingService.cancelLabel(order)
    }
  ];
  
  async execute() {
    const completed: SagaStep[] = [];
    try {
      for (const step of this.steps) {
        await step.execute();
        completed.push(step);
      }
    } catch (err) {
      // Rollback de pasos completados en orden inverso
      for (const step of completed.reverse()) {
        await step.compensate();
      }
      throw new Error(`Saga falló en paso ${completed[0]?.name}`);
    }
  }
}

Explicación

Patrones core:

PatrónProblema ResueltoCompromiso
Event SourcingAudit trail; queries temporalesComplejo; requiere CQRS para reads
CQRSOptimiza modelos de lectura/escritura separadosConsistencia eventual; más código
SagaTransacciones distribuidas sin locksRollback complejo; consistencia eventual
OutboxAtómico “DB update + publicación de mensaje”Requiere proceso relay
Idempotent ConsumerManejar mensajes duplicadosRequiere claves únicas por mensaje

Garantías de ordenamiento de mensajes:

  • Kafka: Ordenado por partition key (ej. order_id)
  • RabbitMQ: Ordenado por cola pero no entre consumers
  • SQS: Sin ordenamiento (usa FIFO queues para ordenamiento)

Variantes

BrokerOrdenamientoDeliveryIdeal Para
KafkaPor particiónAt-least-onceAlto throughput; replayability
RabbitMQPor colaAt-least-onceRouting complejo; colas prioritarias
NATSPor subjectAt-most-onceBaja latencia; simplicidad
PulsarGlobalExactly-onceGeo-replicación; tiered storage

Lo que funciona

  • Diseña eventos como hechos, no comandos: “OrderPlaced” no “PlaceOrder”
  • Incluye versiones de schema: Eventos V1 deben ser legibles por consumers V2
  • Maneja duplicados gracefulmente: Haz consumers idempotentes (upsert, no insert)
  • Monitorea dead letter queues: Mensajes fallidos necesitan investigación, no dropping silencioso
  • Mantén payloads de eventos pequeños: Referencia datos grandes; no embebas blobs

Errores Comunes

  1. Spaghetti event-driven: 50 microservicios suscritos al mismo evento crean acoplamiento invisible
  2. Idempotencia faltante: Procesar el mismo evento de pago dos veces cobra al cliente dos veces
  3. Cadenas síncronas de eventos: Llamar APIs HTTP dentro de event handlers anula el propósito
  4. Sin manejo de dead letter: Mensajes fallidos desaparecen; pierdes eventos de negocio
  5. Suposiciones incorrectas de ordenamiento: Asumir ordenamiento global cuando solo existe por partición

Manejo de Errores y Recuperacion

  • Garantias de delivery de eventos: elige entre at-most-once, at-least-once y exactly-once semantics. At-most-once: fire and forget, menor overhead, puede perder eventos. At-least-once: retry en failure, puede duplicar, requiere idempotency. Exactly-once: transactional, mayor overhead, mas dificil de implementar. La mayoria de sistemas usan at-least-once con consumers idempotent
  • Saga pattern para transacciones distribuidas: usa sagas para mantener consistencia de datos a traves de servicios. Choreography-based: cada servicio emite eventos que triggerean el next step. Orchestration-based: un orchestrator central coordina el saga. Implementa compensating actions para rollback. Trackea saga state en un saga store. Setea timeouts para cada saga step. Monitorea saga completion rate
  • Replay y recuperacion de eventos: storea todos los eventos en un event store para replay. Cuando un servicio recupera despues de downtime, replaya eventos desde la last processed position. Usa checkpointing para trackear eventos procesados. Soporta partial replay para event types especificos. Implementa replay tooling con dry-run mode. Testea procedimientos de replay regularmente
  • Circuit breaker para event consumers: protege downstream services de cascade failures. Tripea circuit breaker despues de N fallos consecutivos. Para de consumir eventos mientras el circuit esta open. Usa half-open state para testear recuperacion. Resetea circuit despues de procesamiento exitoso. Monitorea circuit breaker state a traves de todos los consumers
  • Dead letter queue para eventos: rutea eventos no procesables a una DLQ despues de max retries. Inspecciona eventos de DLQ para patrones de error. Replaya eventos de DLQ despues de fixear root cause. Setea TTL en eventos de DLQ. Alerta en DLQ depth. Usa DLQ separada por event type para remediacion dirigida
  • Deteccion de poison pill: identifica eventos que fallan consistentemente en procesamiento. Trackea failure count por event ID. Mueve a poison queue despues de threshold. Analiza poison events para schema mismatches, payloads invalidos o missing dependencies. Fixea root cause antes de replayar. Loguea detalles de poison events para analisis post-mortem

Performance y Escalabilidad

  • Particionamiento de eventos: particiona eventos por key (ej. order ID, customer ID) para procesamiento paralelo. Eventos con la misma key van a la misma partition, preservando orden. Usa Kafka partitions o SNS+SQS con message group ID. Elije partition count basado en necesidades de throughput. Monitorea partition skew. Rebalancea partitions al agregar consumers
  • Handling de backpressure: cuando la rate de produccion de eventos excede la rate de consumo, aplica backpressure. Usa bounded queues con policy reject-on-full. Aplica rate limiting en el producer. Scalea consumers horizontalmente. Monitorea queue depth y consumer lag. Alerta cuando lag excede threshold. Shedea eventos low-priority durante overload
  • Scaling de consumers: scalea consumers basado en queue depth o processing lag. Usa Kubernetes HPA o AWS Auto Scaling. Scalea por CPU, memory o custom metrics (queue depth). Setea min/max replicas. Monitorea eventos de scaling. Usa consumer groups para procesamiento paralelo. Asegura partition count >= consumer count para distribucion even
  • Optimizacion de serializacion de eventos: elige formatos de serializacion eficientes. JSON: human-readable, payload mas grande. Avro: compact, schema-based, requiere schema registry. Protobuf: compact, language-agnostic, requiere schema. MessagePack: alternativa compacta a JSON. Benchmarka tiempo de serialization/deserialization. Considera compatibilidad de schema evolution

Consideraciones de Seguridad

  • Encriptacion de mensajes: encripta sensitive message payloads en la application layer. TLS para transport encryption. Usa AES-256 para payload encryption. Rota encryption keys trimestralmente. Storea keys en un secrets manager (AWS KMS, HashiCorp Vault). Nunca loguees payloads encriptados. Desencripta solo en la process memory del consumer
  • Autenticacion y autorizacion: autentica producers y consumers usando mutual TLS o SASL. Autoriza queue access via ACLs. Usa per-service credentials con least privilege. Rota credentials regularmente. Audita credential usage. Bloquea conexiones anonimas en produccion. Usa virtual hosts para isolacion de entornos
  • Integridad de mensajes: usa HMAC signatures para verificar message integrity. Firma message body + headers con un shared secret. Verifica signature en consumo. Rechaza mensajes con signatures invalidas. Rota signing keys periodicamente. Loguea failures de signature verification. Usa asymmetric keys para messaging cross-organization
  • Audit logging: loguea todos los eventos de message publishing y consumption. Incluye message ID, timestamp, producer/consumer identity y action. Envia audit logs a un centralized logging system. Reten logs per compliance requirements (ej. 7 años para financial systems). Alerta en patrones sospechosos (mass deletions, unauthorized access)

Monitoreo y Observabilidad

  • Monitoreo de queue depth: monitorea queue depth continuamente. Alerta en depth que excede threshold (ej. 1000 mensajes). Trackea depth trends en el tiempo. Correlaciona depth spikes con eventos de deployment. Usa RabbitMQ management API o cloud provider metrics. Setea Grafana dashboards para visualizacion de queue depth
  • Tracking de consumer lag: trackea la diferencia entre message publish time y processing time. Alerta en lag que excede SLA (ej. 5 minutos). Monitorea lag percentiles (p50, p95, p99). Correlaciona lag con eventos de consumer scaling. Usa distributed tracing para identificar slow consumers. Optimiza procesamiento para reducir lag
  • Metricas de throughput: trackea mensajes publicados por segundo, consumidos por segundo y error rate. Monitorea throughput trends. Alerta en drops de throughput > 50%. Compara throughput a traves de entornos. Usa Prometheus + Grafana para visualizacion. Exporta metricas a una time-series database para analisis historico
  • Monitoreo de error rate: trackea error rate por consumer. Alerta en error rate > 1%. Loguea errores con message context para debugging. Categoriza errores por type (timeout, validation, processing). Monitorea error rate trends. Correlaciona error spikes con deployments o cambios de infraestructura

Deployment y CI/CD

  • Blue-green deployments para consumers: deploya nuevas versiones de consumer usando blue-green strategy. Corre ambas versiones simultaneamente. Drenar la version vieja despues que la nueva version este healthy. Usa feature flags para togglear entre versiones. Monitorea error rates durante switchover. Roll back inmediatamente si error rate aumenta. Testea rollback procedure regularmente
  • Ordering de deployment de consumers: deploya consumers antes de producers al cambiar formatos de mensaje. Esto asegura que nuevos consumers puedan manejar mensajes de format viejo. Usa schema evolution patterns (additive changes, optional fields). Deploya producers despues que consumers esten ready. Usa canary deployment para producers para testear nuevos formatos de mensaje gradualmente
  • Infrastructure as Code para messaging: define queues, exchanges y bindings en Terraform o CloudFormation. Versiona definiciones de infraestructura. Revisa cambios de infraestructura en PRs. Testea cambios de infraestructura en staging antes de produccion. Usa policy-as-code para enforcear security y naming conventions. Taguea todos los messaging resources para cost allocation
  • Versioning de format de mensaje: incluye schema version en message headers. Los consumers manejan multiples versiones durante rollout. Deprecatea versiones viejas despues que todos los consumers upgradeen. Documenta cambios de schema en un changelog. Usa schema registry para Avro/Protobuf. Testea backward y forward compatibility antes de deployar cambios de schema

Pitfalls Comunes y Anti-Patrones

  • Queue compartida para diferentes message types: evita usar una sola queue para diferentes message types. Cada type tiene diferentes requirements de procesamiento, priorities y SLAs. Usa queues separadas por message type o usa routing keys con topic exchanges. Esto habilita scaling, monitoreo y error handling independiente por type
  • No manejar message ordering: algunos sistemas requieren message ordering (ej. state updates). Usa partitioned queues con single-active consumer por partition. Usa sequence numbers para verificacion de ordering. Testea ordering bajo producers concurrentes. Documenta ordering guarantees por queue. Usa FIFO queues cuando se requiere strict ordering
  • Procesamiento syncrono en pipeline async: evita hacer synchronous HTTP calls dentro de message consumers. Esto bloquea el consumer y reduce throughput. Usa async HTTP clients. Setea timeouts apropiados. Mueve slow operations a workers separados. Usa circuit breakers para downstream calls. Monitorea consumer processing time
  • Ignorar consumer lag: consumer lag indica que el sistema no puede mantenerse con la produccion de mensajes. Monitorea lag continuamente. Alerta en lag que excede SLA. Scalea consumers automaticamente basado en lag. Investiga root causes (slow processing, downstream failures, insufficient resources). Trackea lag trends en el tiempo

Optimizacion de Costos

  • Right-sizing de infraestructura de messaging: elige el tier de broker correcto basado en throughput. Empieza con el tier mas pequeño y scalea up basado en metricas. Usa messaging serverless (SQS, SNS) para workloads variables para evitar pagar por idle capacity. Compara costos de RabbitMQ self-hosted vs managed service. Factorea operational overhead de self-hosting. Revisa tier mensualmente
  • Optimizacion de message payload: reduce message size para bajar costos. Comprime large payloads (gzip, lz4). Remueve metadata innecesaria. Usa reference passing (envia un reference ID en lugar de full payload) para large objects. Benchmarka impacto de payload size en throughput y costo. Targetea average message size < 10KB
  • Optimizacion de recursos de consumer: right-sizea consumer instances basado en requirements de procesamiento. Usa spot instances para consumers non-critical. Scalea consumers a zero durante off-hours para queues non-urgent. Monitorea consumer CPU y memory utilization. Right-sizea antes de scalear out. Trackea costo por mensaje procesado
  • Gestion de storage costs: message queues consumen storage para mensajes queued y retained. Setea message TTL apropiado para auto-expirar mensajes viejos. Configura DLQ retention policies. Monitorea queue storage usage. Archiva mensajes viejos a storage mas barato. Usa lifecycle policies para message archives. Trackea storage costs por queue

Testing y Quality Assurance

  • Integration testing para messaging: testea producer-consumer integration end-to-end. Verifica message delivery, ordering y content. Testea con realistic message volumes. Testea escenarios de failure (broker down, consumer crash, network partition). Usa test queues para integration tests. Limpia test queues despues de cada run. Automatiza en CI pipeline
  • Load testing de messaging systems: testea con message volumes production-like. Mide throughput, latency y error rate bajo load. Identifica bottlenecks (broker, consumer, network). Testea consumer scaling behavior. Verifica DLQ behavior bajo load. Testea backpressure handling. Documenta load test results y capacity limits
  • Chaos engineering para messaging: inyecta failures (broker restart, network partition, consumer crash). Verifica que el sistema recupere automaticamente. Testea message redelivery despues de consumer failure. Testea DLQ behavior bajo cascade failures. Corre chaos tests en staging regularmente. Documenta findings y mejora resilience
  • Contract testing para mensajes: usa schema registry o contract testing tools (Pact) para verificar compatibilidad de format de mensaje. Testea producer schema contra consumer expectations. Detecta breaking changes antes de deployment. Corre contract tests en CI. Versiona schemas apropiadamente. Documenta guidelines de schema evolution

Herramientas y Plataformas

  • RabbitMQ Management Plugin: web UI para monitorear RabbitMQ. Visualiza queue depths, message rates, consumer counts. Inspecciona y publica mensajes manualmente. Gestiona exchanges, queues y bindings. Visualiza connection y channel details. Exporta e importa definitions. Habilita en port 15672. Usa para debugging y monitoreo operational
  • AWS SQS y SNS: servicios de messaging managed. SQS para queues point-to-point. SNS para pub/sub. Sin infraestructura que gestionar. Paga por request. Dead letter queues built-in. FIFO queues para ordering. Message attributes para filtering. Usa con Lambda para consumers serverless. Usa con Auto Scaling para EC2 consumers
  • Apache Kafka: plataforma distribuida de event streaming. Alto throughput (millones de eventos/sec). Durable event storage. Consumer groups para procesamiento paralelo. Partitions para ordering. Schema registry para Avro/Protobuf. Usa para event sourcing, log aggregation, stream processing. Self-hosted o managed (Confluent Cloud, AWS MSK)
  • Redis Pub/Sub y Streams: messaging ligero en Redis. Pub/Sub: fire and forget, sin persistencia. Streams: persistente, consumer groups, replayable. Bueno para use cases simples y low latency. Usa para caching invalidation, real-time notifications. No suitable para high-throughput o durable messaging. Usa Redis Streams para reliable delivery

Resumen de Best Practices

  • Siempre usa manual acknowledgment: nunca uses auto-ack en produccion. Procesa el mensaje fully antes de ackear. Usa nack para fallos con estrategia de requeue apropiada. Esto previene message loss en consumer crashes. Monitorea ack/nack rates
  • Setea TTLs apropiados: setea message TTL para prevenir retries infinitos. Setea queue TTL para auto-expirar mensajes stale. Setea DLQ TTL para auto-clean old failures. Elije TTLs basado en business requirements. Documenta valores de TTL por queue. Monitorea TTL expiration rates
  • Usa dead letter exchanges: configura DLX en todas las queues criticas. Setea max delivery count. Monitorea DLQ depth. Implementa DLQ inspection tooling. Crea runbooks para DLQ remediation. Testea DLX configuration regularmente
  • Monitorea todo: trackea queue depth, consumer lag, throughput, error rate y DLQ depth. Setea dashboards. Configura alertas con thresholds apropiados. Usa distributed tracing para message flows. Correlaciona metricas con deployments. Revisa metricas semanalmente

Patrones Avanzados

  • Patron competing consumers: multiples instancias de consumer leen de la misma queue. Cada mensaje es procesado por exactamente un consumer. Habilita horizontal scaling. Usa prefetch=1 para fair dispatch. Monitorea slow consumers que causan distribucion uneven. Usa consumer priority para weighted dispatch. Handlea consumer failures gracefulmente con requeue
  • Patron request-reply: envia un mensaje con una reply-to queue. El consumer procesa y publica la response a la reply queue. Usa correlation IDs para matchear requests con responses. Setea timeouts para replies. Usa para async RPC over messaging. Monitorea reply latency. Usa exclusive reply queues por producer para isolation
  • Patrones de routing key: usa topic exchanges con routing key patterns. * matchea una word. # matchea zero o mas words. orders.*.created matchea orders.us.created y orders.eu.created. orders.# matchea todos los order events. Documenta routing key conventions. Testea routing patterns antes de deployar. Monitorea unroutable messages
  • Priority queues: declara queues con argumento x-max-priority. Setea priority en mensajes via headers. Mensajes de higher priority se consumen primero. Usa sparingly ya que agrega overhead. Monitorea performance de priority queue. Setea max priority a 10 para limitar overhead. Usa queues separadas para diferentes priority levels como alternativa

Compliance y Governance

  • Politicas de retencion de mensajes: define periodos de retencion por queue basado en compliance requirements. Financial systems: 7 años. Healthcare: 6 años. General: 30-90 dias. Implementa retencion via TTL o scheduled cleanup. Documenta politicas de retencion. Audita retention compliance trimestralmente. Usa message archiving antes de deletion para audit trails
  • Data residency para mensajes: algunas regulaciones requieren que los datos se queden dentro de boundaries geograficos especificos. Elije cloud regions cuidadosamente. Usa queues y brokers region-specific. Evita cross-region replication para regulated data. Documenta data residency por queue. Monitorea policy violations. Usa private connections para regulated messaging
  • Access control para queues: restringe queue management a personal autorizado. Usa IAM policies o RabbitMQ ACLs. Separa permisos de read, write y management. Audita queue access. Rota access credentials. Usa per-service accounts. Bloquea anonymous access. Documenta access policies por queue
  • Audit trails de mensajes: loguea todos los eventos de message lifecycle (publish, consume, ack, nack, DLQ). Incluye message ID, timestamp, actor y action. Envia audit logs a immutable storage. Reten per compliance requirements. Soporta export de audit log para regulators. Testea audit trail completeness regularmente

Guia de Troubleshooting

  • Mensajes stuck en queue: chequea si los consumers estan corriendo y conectados. Verifica consumer prefetch settings. Chequea blocked connections (RabbitMQ blocked publisher). Inspecciona consumer logs por errores. Verifica que la queue no este paused. Chequea resource limits (file descriptors, memory). Usa RabbitMQ management UI para inspeccionar queue state
  • High memory usage: chequea large message payloads. Verifica que message TTL este seteado. Chequea unacked messages piling up. Monitorea consumer memory usage. Usa prefetch limits para controlar memoria. Considera message compression. Chequea memory leaks en consumer code. Profilea consumer processes regularmente
  • Connection drops: chequea network stability entre consumers y broker. Verifica heartbeat settings. Chequea broker resource limits. Revisa firewall y load balancer timeouts. Usa automatic recovery en client libraries. Monitorea connection events. Loguea reconnection attempts. Setea appropriate connection timeout
  • Distribucion uneven de mensajes: chequea prefetch settings (muy alto causa uneven distribution). Verifica que todos los consumers tengan equal capacity. Chequea slow consumers. Usa prefetch=1 para strict fair dispatch. Monitorea consumer processing times. Scalea consumers basado en lag. Considera consumer priority para weighted distribution

Preguntas Frecuentes

P: ¿Cuándo debo usar event sourcing vs. CRUD tradicional? R: Usa event sourcing para dominios donde el historial de auditoría, queries temporales o replay son críticos (finanzas, logística). Usa CRUD para dominios simples de CRUD.

P: ¿Cómo manejo evolución de schema en eventos? R: Usa schema registries (Confluent, AWS Glue). Agrega campos; nunca elimines. Mantén compatibilidad hacia atrás por 2+ versiones.

P: ¿Cuál es la diferencia entre sagas de coreografía y orquestación? R: Coreografía: los servicios reaccionan a eventos independientemente. Orquestación: un coordinador central dirige cada paso. La orquestación es más fácil de debug; la coreografía está más desacoplada.

¿Esta solución está lista para producción?

Sí. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno específico antes de desplegar.

¿Cuáles son las características de rendimiento?

El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.

¿Cómo depuro problemas con este enfoque?

Empieza con el ejemplo mínimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.