Event Message¶
1. Nombre del Patrón¶
- Nombre oficial: Event Message
- Categoría: Message Construction (Construcción de Mensajes)
- Traducción contextual: Mensaje de Evento
2. Resumen Ejecutivo¶
Event Message es un patrón de construcción de mensajes cuya semántica es declarativa y factual: el productor notifica que algo ocurrió, sin prescribir al consumidor qué debe hacer con esa información. El mensaje no ordena ni transfiere datos genéricos — informa de un hecho consumado. Es la piedra angular de las arquitecturas event-driven y, probablemente, el tipo de mensaje más transformador de las arquitecturas de software modernas.
El problema que resuelve es profundo: ¿cómo puede un servicio informar a otros servicios de que algo relevante sucedió, sin necesitar conocer qué servicios están interesados, cuántos son, ni qué harán con esa información? Event Message establece un modelo de comunicación donde el productor simplemente declara hechos y los consumidores reaccionan de forma autónoma. Esto invierte la responsabilidad de la coordinación: en lugar de que el productor coordine acciones (enviando comandos a cada servicio), el productor solo publica hechos y cada consumidor decide independientemente cómo reaccionar.
Este patrón es el más importante de los tres tipos de mensaje semántico en las arquitecturas modernas. Los domain events de Domain-Driven Design, los integration events de microservicios, los eventos de event sourcing, los CloudEvents del ecosistema cloud-native, los registros de Kafka topics, las notificaciones de EventBridge, las señales de cambio de estado en systems of record — todos son implementaciones de Event Message. Comprender en profundidad sus variantes, sus consecuencias arquitectónicas y sus patrones derivados es esencial para cualquier arquitecto que trabaje con sistemas distribuidos.
3. Definición Detallada¶
Propósito¶
Event Message establece un contrato semántico en el que el productor publica un mensaje que describe un hecho que ya ocurrió. El mensaje dice "esto sucedió" — en tiempo pasado, como un dato consumado. El propósito es desacoplar al productor de los consumidores a nivel de intención: el productor no sabe ni se preocupa por quién recibirá el evento ni qué hará con él. Solo se compromete a publicar hechos relevantes de su dominio.
Lógica Arquitectónica¶
Event Message introduce una inversión fundamental en la dirección del conocimiento:
- En Command Message: el productor conoce al consumidor (tipo) y le dice qué hacer. La dirección del conocimiento es productor → consumidor.
- En Event Message: el productor no conoce a los consumidores. Solo publica hechos. Los consumidores conocen al productor (se suscriben a sus eventos). La dirección del conocimiento es consumidor → productor.
Esta inversión tiene consecuencias arquitectónicas profundas:
- Cero a N consumidores: un evento puede tener ningún consumidor (si nadie está interesado), uno, o cientos. El productor no necesita cambiar.
- Extensibilidad sin coordinación: añadir un nuevo consumidor de eventos no requiere modificar el productor. El nuevo consumidor simplemente se suscribe al topic.
- Hechos inmutables: un evento es un hecho que ya ocurrió. No puede "rechazarse" ni "deshacerse" — solo puede reaccionarse ante él con acciones compensatorias.
- Pub-sub natural: los eventos se publican en canales pub-sub (topics), no en queues point-to-point. Cada consumidor tiene su propia suscripción.
Principio de Diseño Subyacente¶
El principio es notificación desacoplada de hechos del dominio. El productor es responsable de detectar y publicar hechos relevantes de su dominio. Los consumidores son responsables de suscribirse a los hechos que les interesan y reaccionar según su propia lógica. No hay coordinación directa entre productor y consumidores.
Cuatro Variantes de Event Message¶
Martin Fowler y otros autores distinguen variantes fundamentales de Event Message que tienen consecuencias arquitectónicas muy diferentes:
1. Event Notification¶
El mensaje contiene solo la notificación de que algo ocurrió, con la mínima información necesaria para identificar el hecho:
El consumidor que necesite más detalles debe consultar al servicio productor. Esto minimiza el tamaño del mensaje pero introduce acoplamiento temporal (el consumidor depende de que el productor esté disponible para consultar).
2. Event-Carried State Transfer¶
El mensaje contiene el evento más el estado completo de la entidad:
{"event_type": "OrderPlaced", "order_id": "ORD-12345", "timestamp": "2026-04-07T14:32:15Z",
"order": {"customer_id": "CUST-789", "items": [...], "total": 150.00, "shipping_address": {...}}}
El consumidor tiene todos los datos que necesita sin consultar al productor. Esto elimina el acoplamiento temporal pero produce mensajes más grandes (se cruza con la semántica de Document Message).
3. Domain Event¶
Un evento que tiene significado en el lenguaje ubicuo del dominio, tal como lo define Domain-Driven Design:
{"event_type": "PolicyRenewalApproved", "policy_id": "POL-456", "renewal_term": "2026-2027",
"new_premium": 412.50, "approved_by": "UNDERWRITER-23"}
Los domain events capturan hechos del negocio que son significativos para el bounded context que los produce.
4. Integration Event¶
Un evento diseñado para comunicación entre bounded contexts o entre servicios:
{"event_type": "com.ecommerce.orders.OrderPlaced", "event_id": "evt-abc123",
"aggregate_id": "ORD-12345", "timestamp": "2026-04-07T14:32:15Z",
"data": {"customer_id": "CUST-789", "total": 150.00}}
Los integration events tienen un schema explícito, versionado y diseñado para consumo externo. Los domain events son internos al bounded context; los integration events cruzan fronteras.
Relación con Event Sourcing¶
Event Sourcing es un patrón de persistencia donde el estado de una entidad se almacena como una secuencia de eventos, no como un snapshot. En event sourcing, los eventos son el storage primario — no solo notificaciones. La secuencia OrderCreated → ItemAdded → ItemAdded → OrderSubmitted → PaymentProcessed → OrderShipped reconstruye el estado completo de un pedido.
La relación con Event Message es que los eventos de event sourcing pueden (y suelen) publicarse como Event Messages para consumo de otros servicios. Pero no todos los Event Messages provienen de event sourcing — un servicio con persistencia CRUD tradicional también puede publicar eventos cuando modifica estado.
Relación con Sistemas Distribuidos y Mensajería¶
En la teoría de sistemas distribuidos, Event Message corresponde al concepto de asynchronous broadcast notification. El productor emite un mensaje a un grupo de receptores sin esperar respuesta. Las propiedades del canal (ordering, durability, at-least-once delivery) determinan las garantías del sistema.
En la práctica, Event Message se implementa sobre canales publish-subscribe:
- En Apache Kafka: un topic con múltiples consumer groups. Cada consumer group recibe todos los eventos.
- En RabbitMQ: un fanout exchange que distribuye a múltiples queues (una por consumidor).
- En AWS EventBridge: un event bus con rules que filtran y dirigen eventos a targets.
- En Google Pub/Sub: un topic con múltiples subscriptions.
- En Azure Event Grid: un topic con event subscriptions filtradas.
4. Problema que Resuelve¶
El Problema Antes del Patrón¶
Sin Event Message, cuando algo significativo ocurre en un servicio y otros servicios necesitan saberlo, las opciones son:
- Notificación directa por comando: el servicio que detecta el hecho envía comandos explícitos a cada servicio que necesita saberlo. El productor debe conocer a todos los interesados, mantener una lista de destinatarios y actualizarla cuando se añaden o eliminan consumidores. Esto es el anti-pattern de "smart producer, dumb consumer".
- Polling: los consumidores consultan periódicamente al productor para detectar cambios. Esto produce tráfico innecesario (la mayoría de los polls no detectan cambios), latencia (el cambio se detecta en el siguiente ciclo de polling) y carga en el productor (que debe atender los polls de todos los consumidores).
- Base de datos compartida: los consumidores acceden directamente a la base de datos del productor para detectar cambios (via triggers, CDC manual o queries). Esto acopla a los consumidores al esquema interno del productor.
- No notificar: cada servicio opera con sus propios datos y no reacciona a cambios en otros servicios. Esto produce datos desactualizados, procesos manuales de sincronización y oportunidades perdidas de automatización.
Síntomas del Problema¶
- El productor tiene lógica condicional del tipo "cuando ocurre X, notifica a A, B y C". Cada nuevo consumidor requiere modificar el productor.
- Los servicios detectan cambios con latencia variable porque dependen de polling con diferentes intervalos.
- El productor está sobrecargado atendiendo queries de polling de múltiples consumidores.
- Los datos están desincronizados entre servicios porque no hay un mecanismo oportuno de notificación.
- Añadir un nuevo servicio que necesita reaccionar a un hecho requiere modificar el código del servicio que produce el hecho.
Impacto Operativo y Arquitectónico¶
Sin Event Message:
- La arquitectura es "centrada en el productor" — el productor coordina todas las reacciones a sus propios hechos, violando el Single Responsibility Principle.
- La extensibilidad del sistema está limitada por la capacidad del productor de conocer y coordinar con todos los consumidores.
- La latencia de reacción depende de la frecuencia de polling, no de la frecuencia real de cambios.
- El productor se convierte en un cuello de botella y un single point of coordination.
Riesgos Si No Se Implementa Correctamente¶
- Evento perdido: si un evento no se publica o se pierde en tránsito, los consumidores no se enteran del hecho y operan con información desactualizada. En dominios como finanzas o salud, esto puede tener consecuencias graves.
- Evento duplicado: en sistemas at-least-once, los consumidores pueden recibir el mismo evento más de una vez. Sin idempotencia, esto produce reacciones duplicadas.
- Evento desordenado: si los eventos llegan fuera de orden (
OrderShippedantes deOrderPlaced), los consumidores pueden producir estados inconsistentes. - Evento sin schema: eventos sin un schema versionado producen errores de deserialización cuando el formato evoluciona.
- Acoplamiento temporal oculto: si el evento es de tipo "notification" y el consumidor debe consultar al productor para obtener datos completos, se reintroduce el acoplamiento temporal que el evento pretendía eliminar.
Ejemplos Reales¶
- E-commerce: cuando un cliente completa una compra, el servicio de pedidos publica
OrderPlaced. El servicio de inventario reserva stock, el servicio de pagos inicia el cobro, el servicio de shipping prepara el envío, el servicio de notificaciones envía el email de confirmación y el servicio de analytics registra la conversión. Ninguno de estos servicios es conocido por el servicio de pedidos. - Banca: cuando se procesa una transferencia, el servicio de pagos publica
TransferCompleted. El servicio de notificaciones envía un SMS, el servicio de contabilidad registra el asiento, el servicio de compliance registra la transacción para auditoría y el servicio de analytics actualiza los KPIs. - Healthcare: cuando un médico prescribe un medicamento, el sistema de prescripciones publica
PrescriptionCreated. La farmacia lo recibe para dispensar, el sistema de seguros lo recibe para preautorización, el sistema de interacciones lo recibe para verificar contraindicaciones.
5. Contexto de Aplicación¶
Cuándo Usarlo¶
- Cuando un hecho relevante ocurre en un servicio y otros servicios pueden necesitar saberlo, sin que el productor necesite conocer cuáles ni cuántos.
- Cuando se quiere desacoplar la detección de un hecho de la reacción a ese hecho.
- Cuando múltiples consumidores necesitan reaccionar al mismo hecho, cada uno de manera diferente.
- Cuando se quiere extensibilidad: que nuevos servicios puedan reaccionar a hechos existentes sin modificar el productor.
- En event sourcing: los eventos son el mecanismo de persistencia y el mecanismo de notificación.
- En CQRS: los eventos del write side se publican para actualizar los read models.
- En sagas coreografiadas: los servicios reaccionan a eventos de otros servicios para coordinar transacciones distribuidas.
- Cuando se necesita un audit trail de todo lo que ocurrió en el sistema.
Cuándo No Usarlo¶
- Cuando la intención es que un servicio específico ejecute una acción concreta — usar Command Message.
- Cuando la intención es transferir datos sin semántica temporal — usar Document Message.
- Cuando se necesita una respuesta inmediata del receptor — los eventos son fire-and-forget por naturaleza.
- Cuando el hecho no es relevante fuera del bounded context que lo produce — mantenerlo como domain event interno, no publicarlo como integration event.
- Cuando publicar eventos introduciría complejidad desproporcionada en un sistema simple con pocos servicios y flujos lineales.
Precondiciones¶
- Existe un canal publish-subscribe (topic) para transportar los eventos.
- El productor puede detectar hechos relevantes y construir eventos que los describan.
- Los consumidores pueden suscribirse al topic y procesar eventos de forma asíncrona.
- Existe un contrato (schema) para el formato del evento.
Restricciones¶
- Los eventos son inmutables: una vez publicado, un evento no se modifica ni se elimina. Si hubo un error, se publica un evento corrector.
- Los eventos son facts, not commands: no prescriben acción. Los consumidores deciden qué hacer.
- La entrega es eventual: los consumidores recibirán los eventos, pero no necesariamente de forma inmediata.
- El orden puede estar garantizado solo por partición, no globalmente.
Dependencias¶
- Canal publish-subscribe con retención suficiente.
- Formato de serialización con schema versionado (Avro, Protobuf, CloudEvents + JSON Schema).
- Schema Registry para governance del formato.
- Opcionalmente, herramienta de AsyncAPI para documentar los eventos publicados.
Supuestos Arquitectónicos¶
- Los consumidores son responsables de decidir cómo reaccionar a los eventos.
- El productor no tiene expectativa sobre qué harán los consumidores.
- La entrega at-least-once es la garantía por defecto; los consumidores deben ser idempotentes.
- Los consumidores toleran eventual consistency.
Tipo de Sistemas Donde Aparece con Más Frecuencia¶
- Event-driven architectures (por definición).
- Microservicios con comunicación asíncrona.
- Event sourcing / CQRS.
- Sistemas de IoT (eventos de telemetría).
- Pipelines de datos y analytics (change data capture).
- Sistemas de workflow y orquestación.
- Cualquier sistema que requiera audit trail o reproducibilidad.
6. Fuerzas Arquitectónicas¶
Acoplamiento vs. Extensibilidad¶
Event Message minimiza el acoplamiento entre productor y consumidores. El productor no conoce a los consumidores. Esto maximiza la extensibilidad: añadir consumidores es gratuito desde la perspectiva del productor. Sin embargo, existe un acoplamiento al schema del evento: si el productor cambia el formato del evento, todos los consumidores se ven afectados. Este acoplamiento al schema es el "contrato de integración" mínimo inevitable.
Event Notification vs. Event-Carried State Transfer¶
Esta es la tensión más importante en el diseño de Event Messages. Event Notification (evento mínimo) produce mensajes pequeños pero introduce acoplamiento temporal (el consumidor necesita consultar al productor). Event-Carried State Transfer (evento con datos completos) elimina el acoplamiento temporal pero produce mensajes más grandes y acopla a los consumidores a un modelo de datos más amplio. No hay una respuesta universalmente correcta — depende del caso de uso.
Orden vs. Throughput¶
Garantizar orden global de eventos limita el throughput a un único writer/partition. Particionar por entity_id permite paralelismo con orden por entidad. No garantizar orden permite máximo throughput pero complica la lógica del consumidor. La elección depende de si el orden es relevante para la semántica del negocio.
Granularidad del Evento vs. Ruido¶
Eventos muy granulares (cada cambio de campo produce un evento) proporcionan máxima información pero generan volumen alto y "ruido" (muchos eventos que la mayoría de consumidores ignoran). Eventos gruesos (solo hechos de negocio significativos) reducen el volumen pero pueden perder información que algún consumidor necesita. La granularidad debe alinearse con los hechos relevantes del dominio.
Inmutabilidad vs. Corrección¶
Los eventos son inmutables — representan hechos que ocurrieron. Si un evento se publicó por error, no se puede "borrar" del log. La corrección se hace publicando un evento compensatorio (OrderCancelled para corregir un OrderPlaced erróneo). Esta inmutabilidad es fundamental para audit trail y event sourcing, pero requiere disciplina en el diseño de eventos correctivos.
Domain Events vs. Integration Events¶
Los domain events son internos al bounded context y pueden ser detallados, frecuentes y usar el lenguaje ubicuo del dominio. Los integration events cruzan fronteras y deben ser estables, versionados y diseñados para consumo externo. Publicar domain events directamente como integration events expone detalles internos y acopla a los consumidores a la implementación del productor. La práctica recomendada es transformar domain events en integration events con un schema explícito y estable.
7. Estructura Conceptual del Patrón¶
Actores o Componentes Involucrados¶
- Event Producer (Productor del Evento): el servicio donde ocurre el hecho y que construye y publica el evento.
- Event Channel (Canal del Evento): el canal publish-subscribe (topic) que distribuye el evento a los consumidores.
- Event Consumer (Consumidor del Evento): uno o más servicios que reciben el evento y reaccionan según su propia lógica.
- Schema Registry: el registro que almacena y valida el schema del evento.
- Event Store: opcionalmente, un almacenamiento persistente de todos los eventos (para event sourcing y replay).
Flujo Lógico¶
flowchart TD
A([Event Producer]) --> B[Detectar hecho relevante\nen el dominio]
B --> C[Construir Event Message\ntipo + payload + metadata]
C --> D[(Event Channel / Topic)]
D --> E[Broker almacena y distribuye\na consumidores suscritos]
E --> F[Consumer A recibe evento]
E --> G[Consumer B recibe evento]
F --> H{Es relevante\npara Consumer A?}
H -- Sí --> I[Reaccionar según\nlógica propia de A]
H -- No --> J[Descartar evento]
G --> K[Reaccionar según\nlógica propia de B]
I --> L[Confirmar procesamiento\nack/commit]
J --> L
K --> L
L --> M([Fin]) Responsabilidades¶
| Componente | Responsabilidad |
|---|---|
| Event Producer | Detectar hechos, construir eventos precisos, publicar en canal correcto |
| Event Channel | Distribuir evento a todos los consumidores suscritos, retener para replay |
| Event Consumer | Suscribirse, recibir eventos, decidir cómo reaccionar, ser idempotente |
| Schema Registry | Validar formato del evento, gestionar evolución del schema |
| Event Store | Almacenar la secuencia completa de eventos para replay y auditoría |
Interacciones¶
- Producer → Channel: publicación del evento (publish/produce).
- Channel → Consumer: entrega del evento a cada suscriptor (via consumer group).
- Consumer → Channel: acknowledgment de procesamiento.
- Consumer → (cualquier acción): cada consumidor puede ejecutar cualquier lógica como reacción al evento.
Contratos Implícitos¶
- Tipo del evento: el nombre indica qué hecho ocurrió (OrderPlaced, PaymentProcessed, ShipmentDispatched).
- Schema del payload: los datos incluidos en el evento y sus tipos.
- Semántica de inmutabilidad: el evento describe un hecho consumado; los consumidores no pueden "rechazarlo".
- Ordering: los eventos del mismo aggregate/entity se publican en la partición correcta para preservar orden.
Decisiones de Diseño Clave¶
- Naming: eventos en pasado (
OrderPlaced, noPlaceOrder). El nombre debe describir el hecho, no una instrucción. - Event Notification vs. Event-Carried State Transfer: ¿el evento incluye datos mínimos o el estado completo?
- Domain event vs. Integration event: ¿el evento es interno al bounded context o cruza fronteras?
- Granularidad: ¿un evento por cambio de campo o un evento por transacción de negocio?
- Causation chain: ¿el evento incluye
causation_id(qué evento/comando lo causó) para trazabilidad?
8. Ejemplo Arquitectónico Detallado¶
Dominio: E-commerce — Flujo de Pedido desde Compra hasta Envío¶
Contexto del Negocio¶
Una plataforma de e-commerce procesa 500,000 pedidos diarios. Cada pedido pasa por un flujo que involucra múltiples servicios: creación del pedido, procesamiento del pago, reserva de inventario, preparación para envío, envío y entrega. Cada paso produce hechos que otros servicios necesitan conocer para ejecutar sus propias operaciones.
El flujo no es lineal ni centralizado. Diferentes servicios reaccionan a diferentes hechos de forma independiente. No hay un "orquestador central" — la coordinación emerge de la coreografía de eventos.
Necesidad de Integración¶
Cuando un cliente completa una compra, el servicio de pedidos necesita que múltiples cosas ocurran: cobro del pago, reserva de inventario, preparación del envío, notificación al cliente, actualización de analytics. Pero el servicio de pedidos no debe conocer ni coordinar todas estas acciones. Solo debe publicar el hecho de que el pedido fue creado, y los demás servicios reaccionan autónomamente.
Sistemas Involucrados¶
- Order Service: gestiona el ciclo de vida de los pedidos.
- Payment Service: procesa cobros.
- Inventory Service: gestiona stock.
- Shipping Service: coordina envíos.
- Notification Service: envía emails, SMS, push notifications.
- Analytics Service: registra métricas y KPIs.
- Loyalty Service: gestiona puntos de fidelización.
- Fraud Detection Service: analiza patrones de fraude.
- Kafka Cluster: plataforma de streaming para los eventos.
Diseño de los Event Messages¶
Evento 1: OrderPlaced¶
{
"event_type": "com.ecommerce.orders.OrderPlaced",
"event_id": "evt-2026-04-07-f8a2c3d1",
"aggregate_id": "ORD-2026-00482917",
"aggregate_type": "Order",
"timestamp": "2026-04-07T14:32:15Z",
"version": 1,
"correlation_id": "session-abc123",
"causation_id": "cmd-place-order-xyz",
"schema_version": "2.0",
"data": {
"order_id": "ORD-2026-00482917",
"customer_id": "CUST-00789",
"items": [
{"sku": "SKU-1001", "name": "Wireless Headphones", "quantity": 1, "unit_price": 89.99},
{"sku": "SKU-2045", "name": "USB-C Cable 2m", "quantity": 2, "unit_price": 12.50}
],
"total_amount": 114.99,
"currency": "EUR",
"shipping_address": {
"street": "Calle Serrano 45, 2B",
"city": "Madrid",
"postal_code": "28001",
"country": "ES"
},
"payment_method": "CREDIT_CARD",
"payment_token": "tok_visa_4242"
}
}
Evento 2: PaymentProcessed¶
{
"event_type": "com.ecommerce.payments.PaymentProcessed",
"event_id": "evt-2026-04-07-b3c4d5e6",
"aggregate_id": "PAY-2026-00193847",
"aggregate_type": "Payment",
"timestamp": "2026-04-07T14:32:18Z",
"version": 1,
"correlation_id": "session-abc123",
"causation_id": "evt-2026-04-07-f8a2c3d1",
"data": {
"payment_id": "PAY-2026-00193847",
"order_id": "ORD-2026-00482917",
"amount": 114.99,
"currency": "EUR",
"status": "CAPTURED",
"processor": "stripe",
"processor_reference": "ch_3MqBnK2eZvKYlo"
}
}
Evento 3: ShipmentDispatched¶
{
"event_type": "com.ecommerce.shipping.ShipmentDispatched",
"event_id": "evt-2026-04-07-d7e8f9a0",
"aggregate_id": "SHP-2026-00284716",
"aggregate_type": "Shipment",
"timestamp": "2026-04-07T16:45:30Z",
"version": 1,
"correlation_id": "session-abc123",
"causation_id": "evt-2026-04-07-b3c4d5e6",
"data": {
"shipment_id": "SHP-2026-00284716",
"order_id": "ORD-2026-00482917",
"carrier": "SEUR",
"tracking_number": "SEUR-ES-2026-8472916",
"estimated_delivery": "2026-04-09",
"dispatched_from": "Warehouse Madrid-Sur"
}
}
Decisiones Arquitectónicas¶
-
CloudEvents-inspired envelope: cada evento sigue una estructura inspirada en la especificación CloudEvents con
event_type,event_id,timestampysourceimplícito en el namespace delevent_type. -
Event-Carried State Transfer: los eventos incluyen datos suficientes para que los consumidores no necesiten consultar al productor.
OrderPlacedincluye items, dirección de envío y datos de pago. -
Causation chain: cada evento incluye
causation_idque apunta al evento o comando que lo causó. Esto permite reconstruir la cadena causal:PlaceOrderCommand→OrderPlaced→PaymentProcessed→ShipmentDispatched. -
Correlation ID: todos los eventos del mismo flujo de compra comparten el mismo
correlation_id, permitiendo tracing end-to-end. -
Namespaced event types:
com.ecommerce.orders.OrderPlacedidentifica inequívocamente el bounded context que produce el evento. -
Un topic por bounded context:
order-events,payment-events,shipping-events. Cada bounded context publica sus eventos en su propio topic.
Riesgos y Mitigaciones¶
| Riesgo | Mitigación |
|---|---|
| Evento perdido rompe el flujo | Kafka con acks=all, replication factor 3, min.insync.replicas=2 |
| Consumidor procesa evento duplicado | Idempotency key (event_id) + deduplication en cada consumidor |
| Eventos desordenados por aggregate | Partition key por aggregate_id garantiza orden por entidad |
| Schema incompatible rompe consumidores | Schema Registry con compatibilidad BACKWARD |
| Consumidor lento atrasa su reacción | Consumer lag monitoring + auto-scaling |
| Cadena causal se rompe | Correlation ID + causation ID en cada evento para trazabilidad |
9. Desarrollo Paso a Paso del Ejemplo¶
Paso 1: Cliente Completa la Compra¶
Un cliente en Madrid completa la compra de unos auriculares inalámbricos (89.99 EUR) y dos cables USB-C (12.50 EUR cada uno). El total es 114.99 EUR. La petición llega al Order Service como un HTTP POST.
El Order Service: 1. Valida la petición (items existentes, dirección válida, método de pago registrado). 2. Crea el pedido en su base de datos con estado PLACED. 3. Construye el evento OrderPlaced con todos los datos del pedido. 4. Publica el evento en el topic order-events con key ORD-2026-00482917. 5. Responde al cliente con HTTP 201 Created y el order_id.
Nota crítica: el Order Service NO envía comandos a Payment, Inventory, Shipping ni Notification. Solo publica un evento. La coordinación emerge de los consumidores.
Paso 2: Reacción del Payment Service¶
El Payment Service (consumer group: cg-payments) recibe OrderPlaced:
- Detecta que es un evento relevante para su dominio (un pedido nuevo necesita cobro).
- Inicia el procesamiento del pago usando el
payment_tokenincluido en el evento. - Llama a Stripe para capturar 114.99 EUR.
- Stripe confirma la captura exitosa.
- Publica el evento
PaymentProcesseden el topicpayment-events. - Confirma el procesamiento del evento original (commit offset).
Si el pago falla, publica PaymentFailed, y el Order Service (suscrito a payment-events) reacciona cancelando el pedido.
Paso 3: Reacción del Inventory Service¶
El Inventory Service (consumer group: cg-inventory) recibe OrderPlaced:
- Reserva 1 unidad de SKU-1001 (auriculares) y 2 unidades de SKU-2045 (cables).
- Si el stock es suficiente: actualiza las reservas y publica
InventoryReserved. - Si el stock es insuficiente: publica
InventoryReservationFailed. - Confirma el evento.
El Inventory Service también recibe OrderPlaced de forma independiente al Payment Service. Ambos reaccionan en paralelo. Si el inventario falla pero el pago procede, el Order Service (que escucha ambos) detectará la inconsistencia y coordinará la cancelación y el reembolso.
Paso 4: Reacción del Notification Service¶
El Notification Service (consumer group: cg-notifications) recibe OrderPlaced:
- Busca las preferencias de notificación del cliente
CUST-00789. - Envía un email de confirmación de pedido con los detalles del pedido (items, total, dirección).
- Envía una push notification a la app móvil del cliente.
- Confirma el evento.
Más tarde, cuando reciba ShipmentDispatched, enviará otro email con el número de tracking.
Paso 5: Reacción del Fraud Detection Service¶
El Fraud Detection Service (consumer group: cg-fraud) recibe OrderPlaced:
- Analiza el pedido contra modelos de detección de fraude (monto inusual, dirección nueva, velocidad de compra).
- Califica el riesgo: BAJO (114.99 EUR a una dirección conocida del cliente).
- Si el riesgo fuera ALTO, publicaría
FraudSuspectedy el Order Service reaccionaría bloqueando el pedido. - Confirma el evento.
Paso 6: Cadena de Eventos Continuada¶
Cuando el Payment Service publica PaymentProcessed, el Shipping Service reacciona:
- Recibe
PaymentProcessed(consumer group:cg-shipping). - Verifica que el inventario fue reservado (consultando su local store, alimentado por
InventoryReserved). - Crea una orden de envío y asigna el carrier SEUR.
- Cuando el envío se despacha, publica
ShipmentDispatched.
Cuando ShipmentDispatched se publica: - Notification Service envía email/SMS con tracking number al cliente. - Analytics Service registra el tiempo desde pedido hasta envío. - Loyalty Service asigna puntos de fidelización al cliente. - Order Service actualiza el estado del pedido a SHIPPED.
Paso 7: Visibilidad End-to-End¶
Gracias al correlation_id compartido (session-abc123), un sistema de distributed tracing puede reconstruir la secuencia completa:
PlaceOrderCommand (session-abc123)
└── OrderPlaced (causation: PlaceOrderCommand)
├── PaymentProcessed (causation: OrderPlaced)
│ └── ShipmentDispatched (causation: PaymentProcessed)
│ ├── [Notification: tracking email sent]
│ └── [Loyalty: 115 points awarded]
├── InventoryReserved (causation: OrderPlaced)
├── [Notification: confirmation email sent]
├── [Fraud: risk=LOW]
└── [Analytics: conversion recorded]
10. Diagrama Técnico del Patrón¶
Código Python con diagrams¶
Ver / Copiar código de los diagramas
from diagrams import Diagram, Cluster, Edge
from diagrams.onprem.queue import Kafka
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.monitoring import Grafana
from diagrams.programming.framework import React
from diagrams.onprem.network import Nginx
with Diagram("Event Message - E-commerce Order Flow", show=False, direction="TB"):
with Cluster("Client"):
client = React("Customer\nBrowser")
with Cluster("Order Bounded Context"):
order_api = Nginx("Order API")
order_svc = Server("Order\nService")
order_db = PostgreSQL("Order DB")
with Cluster("Event Channels (Kafka)"):
order_events = Kafka("order-events\n(Pub-Sub)")
payment_events = Kafka("payment-events\n(Pub-Sub)")
shipping_events = Kafka("shipping-events\n(Pub-Sub)")
inventory_events = Kafka("inventory-events\n(Pub-Sub)")
with Cluster("Payment Context"):
payment_svc = Server("Payment\nService")
payment_db = PostgreSQL("Payment DB")
with Cluster("Inventory Context"):
inventory_svc = Server("Inventory\nService")
inventory_db = PostgreSQL("Inventory DB")
with Cluster("Shipping Context"):
shipping_svc = Server("Shipping\nService")
with Cluster("Reactive Consumers"):
notification = Server("Notification\nService")
analytics = Server("Analytics\nService")
fraud = Server("Fraud\nDetection")
loyalty = Server("Loyalty\nService")
monitoring = Grafana("Event Flow\nMonitoring")
# Order flow
client >> order_api >> order_svc >> order_db
order_svc >> Edge(label="OrderPlaced") >> order_events
# Reactions to OrderPlaced
order_events >> Edge(label="react") >> payment_svc >> payment_db
order_events >> Edge(label="react") >> inventory_svc >> inventory_db
order_events >> Edge(label="react") >> notification
order_events >> Edge(label="react") >> fraud
order_events >> Edge(label="react") >> analytics
# Subsequent events
payment_svc >> Edge(label="PaymentProcessed") >> payment_events
inventory_svc >> Edge(label="InventoryReserved") >> inventory_events
payment_events >> Edge(label="react") >> shipping_svc
shipping_svc >> Edge(label="ShipmentDispatched") >> shipping_events
shipping_events >> Edge(label="react") >> notification
shipping_events >> Edge(label="react") >> loyalty
shipping_events >> Edge(label="react") >> order_svc
# Monitoring
order_events >> Edge(style="dotted") >> monitoring
payment_events >> Edge(style="dotted") >> monitoring
shipping_events >> Edge(style="dotted") >> monitoring
from diagrams import Diagram, Cluster, Edge
from diagrams.programming.framework import React
from diagrams.aws.compute import Lambda
from diagrams.aws.database import Dynamodb
from diagrams.aws.integration import Eventbridge
from diagrams.aws.management import Cloudwatch
from diagrams.aws.network import APIGateway
with Diagram("Event Message - E-commerce Order Flow (AWS)", show=False, direction="TB"):
with Cluster("Client"):
client = React("Customer\nBrowser")
with Cluster("Order Bounded Context"):
order_api = APIGateway("API Gateway")
order_svc = Lambda("Order\nService")
order_db = Dynamodb("Order DB")
with Cluster("Event Bus (EventBridge)"):
order_events = Eventbridge("order-events\n(Event Bus)")
payment_events = Eventbridge("payment-events\n(Event Bus)")
shipping_events = Eventbridge("shipping-events\n(Event Bus)")
inventory_events = Eventbridge("inventory-events\n(Event Bus)")
with Cluster("Payment Context"):
payment_svc = Lambda("Payment\nService")
payment_db = Dynamodb("Payment DB")
with Cluster("Inventory Context"):
inventory_svc = Lambda("Inventory\nService")
inventory_db = Dynamodb("Inventory DB")
with Cluster("Shipping Context"):
shipping_svc = Lambda("Shipping\nService")
with Cluster("Reactive Consumers"):
notification = Lambda("Notification\nService")
analytics = Lambda("Analytics\nService")
fraud = Lambda("Fraud\nDetection")
loyalty = Lambda("Loyalty\nService")
monitoring = Cloudwatch("Event Flow\nMonitoring")
# Order flow
client >> order_api >> order_svc >> order_db
order_svc >> Edge(label="OrderPlaced") >> order_events
# Reactions to OrderPlaced
order_events >> Edge(label="rule") >> payment_svc >> payment_db
order_events >> Edge(label="rule") >> inventory_svc >> inventory_db
order_events >> Edge(label="rule") >> notification
order_events >> Edge(label="rule") >> fraud
order_events >> Edge(label="rule") >> analytics
# Subsequent events
payment_svc >> Edge(label="PaymentProcessed") >> payment_events
inventory_svc >> Edge(label="InventoryReserved") >> inventory_events
payment_events >> Edge(label="rule") >> shipping_svc
shipping_svc >> Edge(label="ShipmentDispatched") >> shipping_events
shipping_events >> Edge(label="rule") >> notification
shipping_events >> Edge(label="rule") >> loyalty
shipping_events >> Edge(label="rule") >> order_svc
# Monitoring
order_events >> Edge(style="dotted") >> monitoring
payment_events >> Edge(style="dotted") >> monitoring
shipping_events >> Edge(style="dotted") >> monitoring
from diagrams import Diagram, Cluster, Edge
from diagrams.programming.framework import React
from diagrams.azure.compute import FunctionApps
from diagrams.azure.database import SQLServers
from diagrams.azure.devops import ApplicationInsights
from diagrams.azure.integration import EventGridTopics, ServiceBus, APIManagement
with Diagram("Event Message - E-commerce Order Flow (Azure)", show=False, direction="TB"):
with Cluster("Client"):
client = React("Customer\nBrowser")
with Cluster("Order Bounded Context"):
order_api = APIManagement("API Management")
order_svc = FunctionApps("Order\nService")
order_db = SQLServers("Order DB")
with Cluster("Event Grid (Domain Events)"):
order_events = EventGridTopics("order-events\n(Event Grid Topic)")
payment_events = EventGridTopics("payment-events\n(Event Grid Topic)")
shipping_events = EventGridTopics("shipping-events\n(Event Grid Topic)")
inventory_events = EventGridTopics("inventory-events\n(Event Grid Topic)")
with Cluster("Service Bus (Integration Events)"):
integration_bus = ServiceBus("integration-events\n(Topic + Subscriptions)")
with Cluster("Payment Context"):
payment_svc = FunctionApps("Payment\nService")
payment_db = SQLServers("Payment DB")
with Cluster("Inventory Context"):
inventory_svc = FunctionApps("Inventory\nService")
inventory_db = SQLServers("Inventory DB")
with Cluster("Shipping Context"):
shipping_svc = FunctionApps("Shipping\nService")
with Cluster("Reactive Consumers"):
notification = FunctionApps("Notification\nService")
analytics = FunctionApps("Analytics\nService")
fraud = FunctionApps("Fraud\nDetection")
loyalty = FunctionApps("Loyalty\nService")
monitoring = ApplicationInsights("Application\nInsights")
# Order flow
client >> order_api >> order_svc >> order_db
order_svc >> Edge(label="OrderPlaced") >> order_events
# Domain events fan-out via Event Grid
order_events >> Edge(label="subscription") >> payment_svc >> payment_db
order_events >> Edge(label="subscription") >> inventory_svc >> inventory_db
order_events >> Edge(label="subscription") >> fraud
# Integration events via Service Bus (reliable delivery)
order_events >> Edge(label="forward") >> integration_bus
integration_bus >> Edge(label="subscription") >> notification
integration_bus >> Edge(label="subscription") >> analytics
# Subsequent domain events
payment_svc >> Edge(label="PaymentProcessed") >> payment_events
inventory_svc >> Edge(label="InventoryReserved") >> inventory_events
payment_events >> Edge(label="subscription") >> shipping_svc
shipping_svc >> Edge(label="ShipmentDispatched") >> shipping_events
shipping_events >> Edge(label="subscription") >> notification
shipping_events >> Edge(label="subscription") >> loyalty
shipping_events >> Edge(label="subscription") >> order_svc
# Monitoring
order_events >> Edge(style="dotted") >> monitoring
payment_events >> Edge(style="dotted") >> monitoring
shipping_events >> Edge(style="dotted") >> monitoring
Explicación del Diagrama¶
El diagrama muestra la coreografía de eventos en el flujo de compra de e-commerce:
- El Order Service publica
OrderPlaceden el topicorder-events. - Cinco servicios reaccionan al
OrderPlacedde forma independiente: Payment (cobra), Inventory (reserva), Notification (confirma), Fraud (analiza), Analytics (registra). - El Payment Service publica
PaymentProcessedenpayment-events. - El Inventory Service publica
InventoryReservedeninventory-events. - El Shipping Service reacciona a
PaymentProcessedy publicaShipmentDispatchedenshipping-events. - Notification y Loyalty reaccionan a
ShipmentDispatched. - El Order Service también reacciona a
ShipmentDispatchedpara actualizar el estado del pedido. - Grafana monitorea el flujo de eventos en todos los topics.
La clave del diagrama es que ningún servicio coordina directamente a los demás. Cada servicio publica hechos y reacciona a hechos. La coordinación es emergente.
Correspondencia Patrón ↔ Diagrama¶
| Concepto del Patrón | Componente del Diagrama |
|---|---|
| Event Producer | Order Service, Payment Service, Inventory Service, Shipping Service |
| Event Message | OrderPlaced, PaymentProcessed, InventoryReserved, ShipmentDispatched |
| Event Channel (Pub-Sub) | order-events, payment-events, shipping-events, inventory-events topics |
| Event Consumer | Cada servicio que reacciona a eventos de otros servicios |
| Coreografía | El flujo emerge de las reacciones independientes, sin orquestador central |
| Causation chain | Cada evento referencia al evento que lo causó (visible en correlation_id) |
11. Beneficios¶
Impacto Técnico¶
- Desacoplamiento máximo: el Order Service no conoce a los 5+ servicios que reaccionan a
OrderPlaced. Se pueden añadir nuevos consumidores (un servicio de recomendaciones, un servicio de predicción de demanda) sin modificar el Order Service. - Resiliencia: si el Notification Service está caído cuando se publica
OrderPlaced, los eventos se acumulan en el topic y se procesan cuando el servicio se recupere. El flujo de compra no se interrumpe. - Escalabilidad independiente: cada consumidor escala según su propia carga. El Analytics Service puede necesitar más instancias que el Fraud Detection Service.
- Audit trail nativo: los topics de Kafka con retención suficiente son un registro completo de todos los hechos que ocurrieron en el sistema. Este log es invaluable para debugging, auditoría y compliance.
- Replay: si se necesita reprocesar eventos (por un bug en un consumidor, por un nuevo consumidor que necesita catch-up), se puede rebobinar el offset y reprocesar desde cualquier punto.
- Temporal decoupling: los eventos son asíncronos. El productor no espera a que los consumidores procesen. Esto amortigua picos de carga.
Impacto Organizacional¶
- Autonomía de equipos: el equipo de pedidos, el equipo de pagos, el equipo de shipping y el equipo de notificaciones trabajan de forma completamente independiente. El contrato es el schema del evento, no el código ni la API.
- Extensibilidad organizacional: cuando un nuevo equipo necesita reaccionar a un hecho existente (equipo de loyalty necesita
ShipmentDispatched), se conecta al topic sin coordinación con el equipo que produce el evento. - Domain-driven: los eventos usan el lenguaje del dominio (
OrderPlaced,PaymentProcessed), lo que facilita la comunicación entre equipos técnicos y de negocio.
Impacto Operacional¶
- Visibilidad del flujo: los eventos en los topics documentan implícitamente qué ocurre en el sistema. Herramientas de monitoring permiten visualizar el flujo de eventos en tiempo real.
- Debugging end-to-end: con correlation_id y causation_id, se puede reconstruir la secuencia completa de eventos para cualquier pedido.
- Detección de problemas: un incremento en
PaymentFailedevents o una caída enShipmentDispatchedevents son señales inmediatas de problemas de negocio.
Beneficios de Mantenibilidad y Evolución¶
- Evolución del consumidor: un consumidor puede cambiar completamente su reacción a un evento sin afectar al productor ni a los demás consumidores.
- Migración: un servicio consumidor puede reemplazarse completamente conectando el nuevo servicio al mismo topic.
- Event sourcing: si se necesita reconstruir el estado de una entidad, la secuencia de eventos proporciona un historial completo.
12. Desventajas y Riesgos¶
Complejidad Añadida¶
- Flujo no lineal: en una arquitectura basada en eventos, el flujo de procesamiento no es una secuencia linear visible en un solo servicio. El flujo emerge de las reacciones de múltiples servicios a múltiples eventos. Esto dificulta la comprensión del sistema y el debugging.
- Eventual consistency: los consumidores procesan eventos asincrónicamente. El estado del sistema es eventualmente consistente, no inmediatamente consistente. Esto requiere diseño cuidadoso de la UI y de los procesos de negocio para manejar ventanas de inconsistencia.
- Error handling distribuido: si un consumidor falla al procesar un evento, ¿quién lo sabe? ¿Quién reintenta? ¿Cómo se compensa? El manejo de errores en coreografías basadas en eventos es significativamente más complejo que en flujos síncronos.
- Event schema governance: mantener schemas de eventos estables y versionados cuando múltiples equipos producen y consumen eventos requiere governance activo.
Riesgos de Mal Uso¶
- Evento como comando disfrazado: publicar un evento
OrderNeedsPaymentcon la intención de que el Payment Service lo procese. Si la intención es que un servicio específico haga algo específico, es un Command Message, no un evento. El nombre debe ser un hecho pasado (OrderPlaced), no una instrucción. - Evento sin consumidores: publicar eventos que nadie consume es waste. Aunque el diseño del productor no depende de los consumidores, publicar eventos que no aportan valor a ningún consumidor es overhead innecesario.
- Cadena de eventos circular: Service A publica Evento1, Service B reacciona publicando Evento2, Service A reacciona publicando Evento1. Esto produce un loop infinito. Las cadenas causales deben ser acíclicas.
- Sobre-granularidad: publicar un evento por cada cambio de campo (
OrderLineItemQuantityChanged,OrderShippingAddressUpdated,OrderCustomerNoteModified) en lugar de eventos de negocio significativos (OrderModified) produce un volumen de eventos que abruma a los consumidores.
Sobreingeniería¶
- Event sourcing innecesario: implementar event sourcing (almacenar todo el estado como eventos) cuando un CRUD simple es suficiente. Event sourcing tiene beneficios, pero también complejidad significativa (rebuild de estado, snapshots, migraciones de eventos).
- Coreografía prematura: usar eventos para coordinar 2 servicios con un flujo simple y linear cuando una llamada directa sería más simple y más comprensible.
- Event bus global: un único event bus donde fluyen todos los eventos de todos los servicios. Esto produce un "God Topic" donde es imposible monitorear, filtrar o evolucionar eventos.
Costos de Operación¶
- Storage de eventos: topics con retención larga o infinita consumen storage significativo.
- Monitoreo de múltiples topics: cada topic de eventos necesita monitoreo de throughput, lag y errores.
- Schema Registry: mantener y operar un Schema Registry con compatibility checks.
- Distributed tracing: instrumentar correlation_id y causation_id en todos los eventos y consumidores.
Anti-Patterns Relacionados¶
- Event Sourcing sin Snapshots: reconstruir el estado de una entidad desde millones de eventos sin snapshots intermedios. El rebuild se vuelve prohibitivamente lento.
- Chatty Events: publicar eventos con una frecuencia tan alta que los consumidores no pueden procesarlos (por ejemplo, un evento por cada keystroke del usuario).
- Event-Driven Spaghetti: una arquitectura donde decenas de servicios publican y consumen eventos de forma tan interconectada que nadie puede entender el flujo completo.
13. Relación con Otros Patrones¶
Patrones Complementarios¶
- Command Message (este capítulo): la relación command → event es fundamental. Un comando como
PlaceOrderproduce un evento comoOrderPlaced. En una saga orquestada, el orquestador envía comandos; en una saga coreografiada, los servicios publican eventos. - Document Message (este capítulo): Event-Carried State Transfer es un híbrido evento + documento. El evento incluye el estado completo de la entidad como datos.
- Publish-Subscribe Channel (Capítulo 3): Event Message se publica en canales pub-sub por naturaleza. Cada consumidor tiene su propia suscripción.
- Correlation Identifier (este capítulo): el
correlation_idpermite tracing end-to-end a través de múltiples eventos en un flujo. - Message Expiration (este capítulo): algunos eventos tienen relevancia temporal limitada (por ejemplo,
PriceQuoteGeneratedcon validez de 5 minutos).
Patrones que Suelen Aparecer Antes o Después¶
- Antes: Command Message — frecuentemente un comando causa la acción que produce el evento.
- Después: Command Message — un consumidor de eventos puede enviar comandos a otros servicios como reacción.
- Complementario: Content-Based Router — dirige eventos a diferentes consumidores según su contenido.
- Complementario: Message Filter — un consumidor filtra eventos irrelevantes antes de procesarlos.
- Complementario: Aggregator — múltiples eventos se agregan para producir una reacción (por ejemplo, esperar
PaymentProcessedeInventoryReservedantes de iniciar el envío).
Combinaciones Comunes¶
- Event Message + Event Sourcing: los eventos son tanto el mecanismo de notificación como el mecanismo de persistencia.
- Event Message + CQRS: los eventos del write side alimentan los read models del query side.
- Event Message + Saga (choreography): los servicios publican eventos y reaccionan a eventos de otros servicios para coordinar transacciones distribuidas.
- Event Message + Schema Registry: control de formato y evolución de los schemas de eventos.
- Event Message + Dead Letter Channel: eventos que un consumidor no puede procesar van a una dead-letter queue para inspección.
Diferencias con Patrones Similares¶
- vs. Command Message: el evento informa un hecho pasado; el comando ordena una acción futura. El evento no tiene destinatario específico; el comando tiene un handler. El evento no puede rechazarse; el comando sí.
- vs. Document Message: el evento tiene semántica temporal fuerte (algo ocurrió en un momento); el documento transfiere datos sin semántica temporal. El evento puede ser mínimo (notification); el documento siempre contiene datos.
- vs. Request-Reply: en Request-Reply, hay expectativa de respuesta; en Event Message, el productor no espera respuesta.
Encaje en un Flujo Mayor de Integración¶
Event Message es el tejido conectivo de las arquitecturas event-driven. Es el mecanismo que permite la coreografía de microservicios, la materialización de read models en CQRS, la propagación de cambios entre bounded contexts y la construcción de audit trails. En un flujo completo de integración, los eventos complementan a los comandos: los comandos expresan intenciones, los eventos declaran hechos, y los documentos transfieren datos.
14. Relevancia Actual del Patrón¶
Evaluación: Relevancia Alta — El patrón más importante de las arquitecturas modernas¶
Argumentación¶
Event Message ha pasado de ser un patrón de integración enterprise a ser el paradigma dominante de comunicación en arquitecturas distribuidas modernas:
- Apache Kafka: cada record en un topic de Kafka es un Event Message. Kafka se ha convertido en la plataforma de streaming más adoptada del mundo, con presencia en la mayoría de las empresas Fortune 500.
- AWS EventBridge: un serverless event bus que implementa Event Message como servicio cloud-native. Los eventos son ciudadanos de primera clase en la arquitectura AWS.
- CloudEvents specification: una especificación CNCF para estandarizar el formato de Event Messages entre clouds, frameworks y lenguajes. Adoptada por Azure Event Grid, Knative, CloudEvents SDKs.
- AsyncAPI: una especificación para documentar APIs asíncronas basadas en eventos, equivalente a OpenAPI para REST.
- Domain-Driven Design: los domain events de DDD son la forma estándar de comunicar hechos entre aggregates y bounded contexts.
- Event Sourcing / CQRS: frameworks como EventStoreDB, Axon, Marten, Eventuous implementan Event Message como mecanismo de persistencia y comunicación.
- Serverless: en las arquitecturas serverless (Lambda, Functions, Cloud Run), los eventos son el mecanismo primario de invocación.
Cómo Se Implementa Hoy¶
| Plataforma / Estándar | Implementación de Event Message |
|---|---|
| Apache Kafka | Records en topics con consumer groups |
| AWS EventBridge | Events con schema registry y rules |
| AWS SNS + SQS | SNS notification → SQS queue fan-out |
| Azure Event Grid | Events con event subscriptions y filtros |
| Azure Event Hubs | Event streaming de alto throughput |
| Google Pub/Sub | Messages en topics con subscriptions |
| CloudEvents | Especificación de formato de evento cloud-native |
| AsyncAPI | Especificación de documentación de APIs event-driven |
| EventStoreDB | Event sourcing database nativa |
| Kafka Streams / ksqlDB | Stream processing sobre Event Messages |
| Debezium | Change Data Capture → Event Messages |
| Knative Eventing | Event mesh para Kubernetes |
| NATS JetStream | Event streaming con retención |
Qué Parte Sigue Siendo Esencial¶
- La semántica de hecho consumado: la distinción entre "esto ocurrió" (evento) y "haz esto" (comando) es más relevante que nunca.
- El desacoplamiento productor-consumidor: la capacidad de publicar hechos sin conocer a los consumidores es fundamental para la escalabilidad organizacional de microservicios.
- La inmutabilidad: los eventos como hechos inmutables son la base del event sourcing, el audit trail y la reproducibilidad.
- Event-Carried State Transfer: la práctica de incluir datos en los eventos para eliminar queries síncronas es una de las técnicas más efectivas para resiliencia en microservicios.
- Coreografía de eventos: la coordinación basada en eventos (sin orquestador central) es el modelo dominante en arquitecturas de microservicios.
15. Implementación en Arquitecturas Modernas¶
CloudEvents (Especificación Estándar)¶
{
"specversion": "1.0",
"type": "com.ecommerce.orders.OrderPlaced",
"source": "/orders/order-service",
"id": "evt-2026-04-07-f8a2c3d1",
"time": "2026-04-07T14:32:15Z",
"datacontenttype": "application/json",
"subject": "ORD-2026-00482917",
"data": {
"order_id": "ORD-2026-00482917",
"customer_id": "CUST-00789",
"total_amount": 114.99,
"currency": "EUR"
}
}
CloudEvents estandariza el envelope del evento con campos como specversion, type, source, id, time. Los SDKs de CloudEvents están disponibles para Python, Java, Go, .NET, JavaScript y Rust.
Apache Kafka (Python producer)¶
from confluent_kafka import Producer
from cloudevents.kafka import to_structured
from cloudevents.http import CloudEvent
import json
producer = Producer({"bootstrap.servers": "kafka:9092"})
# Create CloudEvent
event = CloudEvent({
"type": "com.ecommerce.orders.OrderPlaced",
"source": "/orders/order-service",
"subject": "ORD-2026-00482917",
})
event.data = {
"order_id": "ORD-2026-00482917",
"customer_id": "CUST-00789",
"items": [
{"sku": "SKU-1001", "quantity": 1, "unit_price": 89.99},
{"sku": "SKU-2045", "quantity": 2, "unit_price": 12.50}
],
"total_amount": 114.99,
"currency": "EUR"
}
# Produce to Kafka
producer.produce(
topic="order-events",
key="ORD-2026-00482917",
value=json.dumps(event.data).encode("utf-8"),
headers=to_structured(event).headers
)
producer.flush()
AWS EventBridge¶
{
"Source": "com.ecommerce.orders",
"DetailType": "OrderPlaced",
"Detail": "{\"order_id\":\"ORD-2026-00482917\",\"customer_id\":\"CUST-00789\",\"total_amount\":114.99}",
"EventBusName": "ecommerce-events"
}
EventBridge Rules filtran y dirigen eventos a targets (Lambda, SQS, Step Functions):
{
"source": ["com.ecommerce.orders"],
"detail-type": ["OrderPlaced"],
"detail": {
"total_amount": [{"numeric": [">=", 100]}]
}
}
AsyncAPI (Documentación)¶
asyncapi: '2.6.0'
info:
title: Order Events API
version: '2.0.0'
channels:
order-events:
publish:
operationId: publishOrderEvent
message:
oneOf:
- $ref: '#/components/messages/OrderPlaced'
- $ref: '#/components/messages/OrderCancelled'
- $ref: '#/components/messages/OrderShipped'
components:
messages:
OrderPlaced:
name: OrderPlaced
title: Order Placed Event
contentType: application/json
payload:
type: object
required: [order_id, customer_id, total_amount, currency]
properties:
order_id:
type: string
description: Unique order identifier
customer_id:
type: string
total_amount:
type: number
minimum: 0
currency:
type: string
enum: [EUR, USD, GBP]
AsyncAPI documenta los eventos publicados por un servicio, incluyendo el canal, el formato y las versiones. Es el equivalente de OpenAPI para APIs asíncronas basadas en eventos.
Event Sourcing con EventStoreDB¶
import esdbclient
client = esdbclient.EventStoreDBClient(uri="esdb://localhost:2113")
# Append events to a stream (event sourcing)
event = esdbclient.NewEvent(
type="OrderPlaced",
data=json.dumps({
"order_id": "ORD-2026-00482917",
"customer_id": "CUST-00789",
"total_amount": 114.99
}).encode("utf-8"),
metadata=json.dumps({
"correlation_id": "session-abc123",
"causation_id": "cmd-place-order-xyz"
}).encode("utf-8")
)
client.append_to_stream(
stream_name="Order-ORD-2026-00482917",
current_version=esdbclient.StreamState.NO_STREAM,
events=[event]
)
# Subscribe to all events (integration)
subscription = client.subscribe_to_all()
for event in subscription:
print(f"Received: {event.type} for stream {event.stream_name}")
EventStoreDB almacena eventos como el mecanismo de persistencia primario y permite suscripción para notificación a otros servicios.
Azure Event Grid¶
Topic: ecommerce-orders
Event Type: com.ecommerce.orders.OrderPlaced
Subscription: payment-handler
Filter: eventType = 'com.ecommerce.orders.OrderPlaced'
Endpoint: https://payment-service.azurewebsites.net/api/events
Subscription: notification-handler
Filter: eventType IN ('OrderPlaced', 'ShipmentDispatched')
Endpoint: https://notification-service.azurewebsites.net/api/events
Azure Event Grid es serverless, con entrega at-least-once, retry automático y dead-lettering nativo.
16. Consideraciones de Gobierno y Operación¶
Observabilidad¶
- Métricas por topic de eventos: events-published/sec (por event type), events-consumed/sec (por consumer group), consumer lag, event processing time, dead-letter count.
- Distributed tracing: propagar
correlation_idycausation_iden todos los eventos para reconstruir cadenas causales completas. Integrar con Jaeger, Zipkin o OpenTelemetry. - Event catalog: mantener un catálogo de todos los tipos de eventos publicados en la organización, con su schema, productor, consumidores y SLAs. Herramientas como AsyncAPI, Backstage o custom event catalogs.
Monitoreo¶
- Consumer lag: la métrica más crítica. Si un consumidor se atrasa, su reacción a los eventos será tardía.
- Event flow rate: un cambio abrupto en la tasa de eventos (incremento o caída) indica un problema de negocio o técnico.
- End-to-end latency: el tiempo desde la publicación del evento hasta la reacción del último consumidor. Definir SLAs por tipo de evento.
- Dead letter rate: la tasa de eventos que terminan en DLQ indica problemas de procesamiento en los consumidores.
- Schema validation errors: errores de deserialización indican incompatibilidades de schema.
Versionado¶
- Schema Registry con BACKWARD compatibility: los consumidores existentes pueden procesar eventos con nuevos schemas (ignoran campos desconocidos, usan defaults para campos ausentes).
- Event type versioning: para cambios incompatibles, crear un nuevo tipo de evento (
OrderPlacedV2) y publicar ambas versiones durante un período de migración. - AsyncAPI versioning: mantener la documentación AsyncAPI versionada junto con el código del servicio productor.
Seguridad¶
- Topic ACLs: definir quién puede publicar y quién puede consumir en cada topic de eventos.
- Event content encryption: para eventos con datos sensibles, cifrar campos sensibles en el payload.
- PII handling: evaluar si los eventos contienen Personally Identifiable Information y aplicar las políticas de data governance correspondientes (anonimización, pseudonimización, data retention).
Manejo de Errores¶
- Consumer retry con backoff: errores transitorios se reintentan con backoff exponencial.
- Dead letter queue: eventos que fallan todos los reintentos van a DLQ para inspección manual.
- Compensating events: si un consumidor necesita "deshacer" la reacción a un evento, publica un evento compensatorio (nunca modifica el evento original).
- Poison message detection: detectar eventos que causan errores sistemáticos y segregarlos antes de que bloqueen el procesamiento.
Idempotencia¶
- Event ID deduplication: cada evento tiene un
event_idúnico. Los consumidores almacenan los IDs procesados y descartan duplicados. - Idempotent reactions: las reacciones de los consumidores deben ser idempotentes. Enviar el mismo email dos veces es peor que no enviarlo — el consumidor debe verificar si ya reaccionó a ese evento.
Auditoría¶
- Los topics de eventos con retención suficiente son un audit trail completo de todos los hechos del sistema.
- Registrar qué consumidores procesaron cada evento, cuándo y con qué resultado.
- Mantener un catálogo de eventos con metadata de governance (data owner, data classification, retention policy).
Performance¶
- Batching: publicar eventos en batch para amortizar overhead de red.
- Compression: comprimir payloads de eventos (LZ4, Zstd) para reducir bandwidth y storage.
- Partitioning strategy: la partition key determina tanto el orden como el paralelismo. Elegir con cuidado.
- Consumer prefetch: configurar prefetch para que los consumidores tengan eventos listos.
Escalabilidad¶
- Horizontal consumer scaling: añadir instancias a un consumer group para más paralelismo.
- Topic partitioning: más particiones permiten más consumidores paralelos.
- Auto-scaling: escalar consumidores basándose en consumer lag (KEDA, Lambda concurrency).
- Tiered storage: para topics con retención larga, usar tiered storage para mover segmentos antiguos a almacenamiento barato.
17. Errores Comunes¶
Nombrar Eventos en Imperativo¶
El error más frecuente y más revelador de confusión semántica. Un evento llamado ProcessPayment o SendNotification es un comando disfrazado de evento. Los eventos deben nombrarse en tiempo pasado: PaymentProcessed, NotificationSent. Si la intención es que un servicio haga algo, es un Command Message.
Publicar Eventos con Acoplamiento de Consumidor¶
Un evento llamado OrderPlacedForPaymentProcessing delata que el productor está pensando en un consumidor específico. Los eventos deben describir el hecho sin referencia a quién lo consumirá. OrderPlaced es correcto — qué servicios reaccionan es responsabilidad de los consumidores, no del productor.
Event Notification sin Datos Suficientes¶
Publicar OrderPlaced con solo order_id y que 5 consumidores llamen al Order Service para obtener los datos completos del pedido. Esto reintroduce acoplamiento temporal y sobrecarga al productor. En la mayoría de casos, Event-Carried State Transfer (incluir los datos relevantes en el evento) es la mejor opción.
No Manejar Eventual Consistency en la UI¶
La UI muestra "Pedido creado" pero cuando el usuario refresca, el inventario aún no se ha reservado y el pago no se ha procesado. Sin manejo de eventual consistency en la UI (estados intermedios, polling, websockets), el usuario ve inconsistencias que generan desconfianza.
Cadenas Causales Sin Límite¶
Service A publica Evento1, Service B reacciona y publica Evento2, Service C reacciona y publica Evento3, y así sucesivamente. Sin un límite en la profundidad de la cadena causal, un solo evento inicial puede desencadenar una cascada de eventos difícil de predecir y debuggear. Las cadenas causales deben ser acotadas y documentadas.
No Implementar Dead Letter Queue¶
Eventos que no pueden procesarse (por un bug en el consumidor, por datos corruptos, por una dependencia no disponible) se reintentan infinitamente sin ir a dead-letter. Esto bloquea el consumo de eventos posteriores en la misma partición, causando un "stuck consumer" que no procesa nada.
Confundir Domain Events con Integration Events¶
Publicar domain events internos (con detalles de implementación, nombres de tablas, IDs internos) como integration events que cruzan fronteras de bounded context. Los integration events deben tener un schema estable, diseñado para consumo externo, sin exponer detalles internos del productor.
Event Sourcing para Todo¶
Aplicar event sourcing a todos los servicios, incluyendo aquellos con lógica CRUD simple, produce complejidad desproporcionada. Event sourcing aporta valor en dominios con requisitos de auditoría, undo, temporal queries o modelado de procesos de negocio complejos. En un servicio de configuración con 10 settings, CRUD es más apropiado.
18. Conclusión Técnica¶
Event Message es el patrón más transformador de las arquitecturas de software modernas. Su semántica — "esto ocurrió" — invierte la dirección del conocimiento en la comunicación entre servicios: el productor publica hechos sin conocer a los consumidores, y los consumidores reaccionan autónomamente. Esta inversión es la base del desacoplamiento extremo, la extensibilidad sin coordinación y la resiliencia ante fallos parciales que caracterizan a las arquitecturas event-driven.
Cuándo aporta valor: siempre que un hecho en un servicio sea relevante para otros servicios y la lista de servicios interesados pueda crecer sin que el productor lo sepa. Los escenarios más frecuentes son microservicios con coreografía, CQRS (propagación del write side al read side), event sourcing, audit trail, analytics en tiempo real y notificaciones reactivas.
Cuándo evita problemas importantes: Event Message evita el anti-pattern del "smart producer" que coordina directamente a todos los consumidores, produciendo un sistema frágil donde cada nuevo consumidor requiere modificar el productor. También evita el polling ineficiente y la base de datos compartida como mecanismo de detección de cambios.
Cuándo no conviene adoptarlo: cuando el sistema tiene pocos servicios con flujos simples y lineales, la complejidad de una arquitectura event-driven no se justifica. Cuando se necesita respuesta inmediata y síncrona, los eventos asíncronos no son el mecanismo adecuado. Cuando la intención es que un servicio específico ejecute una acción, un Command Message es más correcto.
Recomendación para arquitectos: adopte Event Message como el mecanismo primario de comunicación entre bounded contexts en una arquitectura de microservicios. Nombre los eventos en tiempo pasado (OrderPlaced, no PlaceOrder). Incluya datos suficientes en el evento (Event-Carried State Transfer) para que los consumidores no necesiten consultar al productor. Use CloudEvents como formato estándar y AsyncAPI para documentar. Establezca un catálogo de eventos con Schema Registry y governance desde el inicio. Monitoree consumer lag como la métrica más crítica de salud del sistema. Y mantenga siempre la disciplina de separar hechos (events) de instrucciones (commands): esta distinción, más que cualquier decisión de tecnología, define la calidad de una arquitectura event-driven.


