Arquitectura basada en eventos
Cuando una aplicación crece, uno de los problemas que antes o después aparece no está necesariamente en la lógica de negocio ni en la cantidad de código, sino en la forma en que sus diferentes partes se comunican. Un componente necesita avisar a otro de que algo ha sucedido, ese segundo componente necesita informar a un tercero y, con el tiempo, una operación aparentemente sencilla termina recorriendo una cadena de llamadas entre módulos, servicios y sistemas. El problema no es que exista comunicación, sino que cada nueva dependencia directa hace que los componentes conozcan más cosas unos de otros y que cualquier cambio tenga un radio de impacto mayor.
El modelo tradicional de comunicación suele apoyarse en una interacción de tipo petición-respuesta: un componente necesita algo y llama directamente a otro. Es un modelo sencillo, explícito y muy útil, pero no siempre representa bien la naturaleza de todos los procesos que ocurren en un sistema. Hay situaciones en las que un componente no necesita realmente pedirle algo a otro, sino simplemente comunicar que ha ocurrido un acontecimiento que puede ser relevante para diferentes partes del sistema. Es precisamente en este punto donde aparece la arquitectura basada en eventos (Event-Driven Architecture, AEA): una forma de diseñar sistemas en la que los acontecimientos relevantes se convierten en elementos de comunicación y otros componentes pueden reaccionar ante ellos sin que exista necesariamente una relación directa entre quien produce el acontecimiento y quien lo consume.
Del «haz esto» al «ha ocurrido esto»
La diferencia fundamental puede entenderse comparando dos tipos de mensajes. Un comando expresa una intención: «crear este pedido», «cancelar esta reserva» o «cambiar la dirección de este cliente». Un evento, en cambio, representa un hecho que ya ha ocurrido: «pedido creado», «reserva cancelada» o «dirección del cliente actualizada». La diferencia parece pequeña, pero cambia considerablemente la relación entre los componentes: un comando pide que algo suceda; un evento comunica que algo ha sucedido.
Imaginemos una aplicación de comercio electrónico. Cuando un cliente confirma un pedido, una implementación tradicional podría hacer que el servicio de pedidos llamase directamente al servicio de inventario para reservar los productos, al servicio de pagos para cobrar el importe y al servicio de notificaciones para enviar un correo. El servicio de pedidos conoce entonces la existencia de esos tres componentes y, además, necesita conocer cómo comunicarse con ellos. En un diseño basado en eventos, el servicio de pedidos puede limitarse a completar su propia operación y publicar PedidoCreado; a partir de ahí, inventario, pagos, notificaciones u otros componentes pueden reaccionar ante ese acontecimiento según sus propias responsabilidades.
La diferencia no consiste simplemente en sustituir una llamada HTTP por un mensaje. Lo importante es cambiar el significado de la relación entre los componentes. En el primer caso, el productor está diciendo «haz algo por mí» a un componente concreto; en el segundo, está diciendo «esto ha ocurrido» y deja que los componentes interesados decidan si necesitan reaccionar.
Los eventos como acontecimientos relevantes
No cualquier cambio interno de una aplicación debería convertirse en un evento. Que una fila de una base de datos haya cambiado, que se haya ejecutado un método o que una variable haya adoptado un nuevo valor no significa necesariamente que exista un acontecimiento relevante para el resto del sistema. Un evento adquiere valor cuando representa algo que otros componentes pueden necesitar conocer para realizar su propia actividad.
Por ejemplo, una aplicación puede modificar internamente diez campos cuando un cliente cambia su dirección. La mayoría de esos cambios son detalles de implementación que no necesitan salir de ese componente. Sin embargo, el hecho de que la dirección del cliente haya sido actualizada sí puede ser relevante para otros sistemas, por lo que podemos representarlo mediante un evento como DireccionClienteActualizada. El evento establece así una frontera entre lo que ocurre dentro de un componente y aquello que ese componente considera suficientemente importante como para comunicar al exterior.
En la práctica, la palabra «evento» puede utilizarse tanto para referirse al acontecimiento conceptual como al mensaje que lo representa dentro del sistema. Conviene mantener presente la diferencia: primero existe un hecho significativo —por ejemplo, que un pedido ha sido creado— y después decidimos cómo representar y transportar esa información para que otros componentes puedan reaccionar ante ella.
Eventos de dominio y eventos de integración
Esta distinción adquiere especial importancia cuando introducimos el concepto de diseño guiado por el dominio (Domain-Driven Design, DDD), una aproximación que pone el conocimiento del negocio y sus conceptos en el centro del diseño del software. Dentro de un dominio podemos identificar acontecimientos que tienen significado para la propia lógica de negocio. Por ejemplo, en un sistema de pedidos podemos considerar eventos de dominio PedidoCreado, ProductoReservado o PagoConfirmado. Estos eventos permiten que diferentes partes de ese dominio reaccionen ante cambios relevantes sin tener que establecer necesariamente llamadas directas entre ellas.
Pero cuando ese acontecimiento necesita atravesar la frontera de un dominio, servicio o aplicación, aparece otro concepto: el evento de integración. Su propósito es comunicar a otro componente o sistema que se ha producido algo relevante, utilizando para ello un contrato que ambos puedan entender. No tiene por qué coincidir exactamente con la representación interna utilizada dentro del dominio. Por ejemplo, nuestro dominio puede trabajar internamente con PedidoCreado, mientras que para un sistema externo podemos publicar PedidoCreadoParaFacturacion con únicamente la información necesaria para que el sistema de facturación pueda realizar su trabajo.
La diferencia puede resumirse de una manera sencilla: el evento de dominio expresa el acontecimiento desde la perspectiva del dominio que lo produce; el evento de integración expresa aquello que necesitamos comunicar fuera de esa frontera. Esta separación evita que el modelo interno de una aplicación se convierta accidentalmente en su contrato de integración. En sistemas pequeños ambos conceptos pueden acabar representados físicamente mediante el mismo mensaje, pero mantener la distinción conceptual resulta útil porque nos obliga a preguntarnos quién necesita conocer el acontecimiento y qué información necesita realmente.
Podemos visualizarlo así:
DOMINIO DE PEDIDOS
│
│ Evento de dominio
▼
PedidoCreado
│
│ transformación
▼
Evento de integración
PedidoCreadoParaFacturacion
│
▼
SISTEMA DE FACTURACIÓN
Esta separación también permite que un único acontecimiento de dominio dé lugar a diferentes comunicaciones externas. El hecho PedidoCreado podría generar un evento de integración para facturación, otro para analítica y otro para un sistema de logística, cada uno con un contrato adaptado a su consumidor. El dominio expresa qué ha ocurrido; la integración decide qué parte de ese acontecimiento debe hacerse visible a cada frontera.
Las fuentes de eventos: dónde comienza el acontecimiento
Todo evento tiene un origen. Puede producirse dentro de una aplicación cuando una operación modifica el estado del negocio, en un dispositivo físico que detecta una situación, en otro sistema empresarial o incluso como consecuencia de una interacción humana. Lo importante es que en algún punto existe una fuente capaz de detectar que ha ocurrido algo que merece ser comunicado.
Pensemos en una plataforma logística. Un lector situado en la entrada de un almacén puede detectar que un paquete ha llegado. La aplicación que recibe esa información puede convertirla en un acontecimiento de negocio como PaqueteRecibidoEnAlmacen. A partir de ahí, diferentes componentes podrían actualizar el inventario, informar al sistema de seguimiento, iniciar una preparación de envío o registrar información para analítica. La fuente conoce que el paquete ha llegado, pero no necesita conocer necesariamente todas las reacciones que ese acontecimiento provocará.
La misma idea aparece en otros dominios: PedidoEnviado, PagoConfirmado, ContratoFirmado o UsuarioRegistrado son acontecimientos que pueden tener consecuencias en diferentes partes de una organización. El valor de una arquitectura basada en eventos aparece precisamente cuando somos capaces de identificar estos acontecimientos relevantes y convertirlos en puntos de comunicación entre responsabilidades diferentes.
Publicadores: comunicar sin conocer a los consumidores
El componente que pone un evento a disposición de otros componentes actúa como publicador (publisher). Su responsabilidad es comunicar el acontecimiento, no decidir necesariamente quién va a reaccionar ante él. Esta idea está estrechamente relacionada con el patrón Publish/Subscribe, en el que los productores publican mensajes y los consumidores se suscriben a los acontecimientos que les interesan.
La diferencia frente a una llamada directa es importante. Si el servicio de pedidos llama directamente al servicio de inventario, ambos están relacionados explícitamente: el primero necesita conocer al segundo y saber cómo solicitarle la operación. Si publica PedidoCreado, puede existir un canal intermedio a través del cual diferentes consumidores reciban el evento sin que el productor tenga que conocerlos individualmente. El publicador comunica el hecho, mientras que la decisión de reaccionar ante él queda en manos de los consumidores.
Esto no significa que las dependencias desaparezcan por completo. Simplemente cambian de naturaleza. Ya no dependemos necesariamente de la existencia de un servicio concreto, pero sí dependemos del significado y del contrato del evento. Si publicamos PedidoCreado, los consumidores necesitan saber qué significa ese evento y qué información contiene. El desacoplamiento, por tanto, no consiste en eliminar toda dependencia, sino en evitar dependencias directas entre implementaciones y sustituirlas por contratos de comunicación más estables.
Suscriptores: reaccionar ante lo que sucede
Al otro lado encontramos los suscriptores o consumidores. Un consumidor recibe aquellos eventos que le interesan y decide qué hacer con ellos de acuerdo con sus propias responsabilidades. Un mismo acontecimiento puede tener múltiples consumidores y cada uno puede reaccionar de una manera completamente diferente.
Volvamos al evento PedidoCreado. El servicio de inventario puede utilizarlo para reservar productos; el servicio de facturación puede generar una factura; el servicio de notificaciones puede enviar un correo y un sistema analítico puede registrar el pedido para sus métricas. Ninguno necesita necesariamente conocer la implementación interna de los demás. Cada consumidor interpreta el evento desde el punto de vista de su propio dominio o responsabilidad y decide qué debe hacer con la información recibida.
Esta característica hace que una arquitectura basada en eventos pueda evolucionar de una forma interesante. Podemos incorporar un nuevo consumidor que necesite reaccionar ante PedidoCreado sin modificar necesariamente el componente que produjo el evento. Por ejemplo, meses después de poner en producción la aplicación podemos incorporar un sistema de análisis de fraude que quiera observar los pedidos creados. Si el evento ya forma parte del contrato de comunicación, el nuevo consumidor puede suscribirse sin introducir una nueva llamada en el servicio de pedidos.
El intermediario: el punto de encuentro
Entre publicadores y suscriptores suele aparecer un intermediario de mensajes (message broker) o event broker. Su función es proporcionar el mecanismo mediante el cual los mensajes pueden publicarse, almacenarse, distribuirse y entregarse a los consumidores correspondientes. Dependiendo de la tecnología y de la configuración, puede encargarse también de aspectos como colas, persistencia, enrutamiento, confirmaciones o reintentos. Broker de mensajería en Wikipedia
Podemos imaginarlo como una oficina de correos. El productor entrega un mensaje indicando qué ha ocurrido y no necesita ir puerta por puerta buscando quién podría estar interesado. El intermediario se encarga de poner ese mensaje a disposición de los consumidores según las reglas establecidas. El intermediario se convierte así en uno de los mecanismos que hacen posible el desacoplamiento entre quienes producen acontecimientos y quienes reaccionan ante ellos.
┌──────────────┐
│ Productor │
│ PedidoCreado │
└──────┬───────┘
│
▼
┌───────────────────┐
│ Event / Message │
│ Broker │
└─────┬─────┬──────┘
│ │
▼ ▼
┌─────────┐ ┌──────────────┐
│Inventario│ │ Notificaciones│
└─────────┘ └──────────────┘
La existencia de un intermediario no convierte automáticamente una aplicación en una arquitectura basada en eventos. Lo importante sigue siendo la forma en que utilizamos la comunicación. Podemos tener un broker y seguir diseñando componentes fuertemente acoplados; del mismo modo, podemos implementar comunicación basada en eventos con mecanismos mucho más sencillos. La tecnología facilita el patrón, pero no sustituye la decisión arquitectónica.
Los manejadores de comandos: iniciar una operación
Aunque una arquitectura basada en eventos pone el foco en los acontecimientos, los comandos siguen teniendo un papel importante. Alguien tiene que iniciar muchas de las operaciones que provocan esos acontecimientos. Un usuario puede solicitar CrearPedido, otro sistema puede enviar CancelarReserva o una aplicación puede pedir CambiarDireccionCliente.
El componente encargado de procesar esa intención suele denominarse manejador de comandos (command handler). Recibe el comando, valida lo necesario, ejecuta la lógica correspondiente y modifica el estado de la aplicación. Como consecuencia de esa operación puede producirse uno o varios eventos. El flujo podría ser el siguiente:
Usuario
│
│ CrearPedido
▼
┌──────────────────┐
│ Manejador comando│
└────────┬─────────┘
│
▼
Base de datos
│
│ Pedido creado
▼
PedidoCreado
│
▼
Broker
/ \
/ \
▼ ▼
Inventario Notificaciones
Aquí vuelve a aparecer la diferencia fundamental entre comandos y eventos. CrearPedido representa una intención que estamos solicitando al sistema; PedidoCreado representa el resultado o hecho que el sistema está comunicando una vez que la operación ha ocurrido. Los comandos expresan lo que queremos que suceda; los eventos expresan lo que ya ha sucedido. El manejador de comandos, además, no es exclusivo de las arquitecturas basadas en eventos: es un mecanismo de procesamiento de intenciones que puede utilizarse también en arquitecturas que no emplean eventos.
Cómo encajan todas las piezas
Cuando observamos el conjunto, podemos empezar con una interacción aparentemente convencional. Un usuario solicita crear un pedido, la aplicación recibe el comando y un manejador ejecuta la operación correspondiente. Una vez que el pedido ha sido creado, el sistema publica PedidoCreado. El intermediario distribuye ese acontecimiento a los consumidores interesados y cada uno ejecuta la reacción que corresponda a su responsabilidad.
El flujo completo puede representarse de una manera simplificada:
Usuario
│
│ Comando: CrearPedido
▼
Aplicación de pedidos
│
│ modifica estado
▼
Pedido creado
│
│ Evento: PedidoCreado
▼
Broker
├──────────────► Inventario
├──────────────► Facturación
├──────────────► Notificaciones
└──────────────► Analítica
Lo interesante es que la arquitectura deja de organizarse únicamente alrededor de las preguntas «¿a qué servicio tengo que llamar?» y «¿qué método tengo que invocar?». Empieza a organizarse también alrededor de preguntas como «¿qué acontecimientos son importantes en nuestro negocio?», «¿quién necesita conocerlos?» y «¿qué responsabilidad debe asumir cada consumidor cuando ocurren?». Ese cambio de perspectiva es probablemente más importante que cualquiera de las tecnologías concretas utilizadas para implementarlo.
Un ejemplo completo: desde que el cliente compra hasta que el pedido llega al almacén
Podemos ver todos estos conceptos juntos mediante un ejemplo sencillo de comercio electrónico. Supongamos que un cliente compra un producto y pulsa el botón «Confirmar pedido». La aplicación recibe el comando CrearPedido, que es procesado por el manejador correspondiente. Este valida la información, registra el pedido y, si la operación se completa correctamente, el dominio genera el evento PedidoCreado. Hasta este punto estamos principalmente dentro del dominio de pedidos: hemos pasado de una intención del usuario a un nuevo hecho de negocio.
Cliente
│
│ CrearPedido
▼
┌───────────────────┐
│ Servicio de │
│ pedidos │
└─────────┬─────────┘
│
│ Pedido creado
▼
PedidoCreado
Ahora podemos introducir la distinción entre evento de dominio y evento de integración. Dentro del dominio puede ser suficiente trabajar con PedidoCreado, pero si necesitamos comunicar el acontecimiento a otros sistemas, podemos transformar ese hecho en un evento de integración como PedidoCreadoParaInventario. Este evento puede contener, por ejemplo, el identificador del pedido y las líneas que necesitan ser procesadas, sin exponer toda la estructura interna del agregado o del modelo de pedidos. De esta forma, el contrato de integración se diseña para las necesidades del consumidor y no como una copia accidental del modelo interno del productor.
DOMINIO DE PEDIDOS
│
│ PedidoCreado
▼
Evento de integración
PedidoCreadoParaInventario
│
▼
Broker
│
▼
SISTEMA DE INVENTARIO
El sistema de inventario recibe el evento y comprueba si existe stock suficiente. Si puede reservar las unidades solicitadas, realiza su propia operación y genera otro acontecimiento: StockReservado. Ese nuevo evento no tiene por qué ser una respuesta directa al servicio de pedidos. Es un hecho independiente que puede interesar a otros consumidores. Por ejemplo, el sistema de preparación de envíos puede reaccionar ante StockReservado y comenzar a preparar el paquete.
Al mismo tiempo, otros consumidores pueden reaccionar ante acontecimientos anteriores. Facturación puede recibir un evento relacionado con la confirmación del pedido y generar la factura; notificaciones puede enviar un correo al cliente; analítica puede registrar el pedido. Ninguno de estos componentes necesita que el servicio de pedidos realice una llamada directa a cada uno. La cadena de comportamiento se construye mediante acontecimientos que van apareciendo como consecuencia de las responsabilidades de cada componente.
CrearPedido
│
▼
PedidoCreado
│
▼
┌───────────────┐
│ Broker │
└─┬─────┬─────┬─┘
│ │ │
▼ ▼ ▼
Stock Fact. Notif.
│
▼
StockReservado
│
▼
Preparación de envío
│
▼
PedidoPreparado
│
▼
Envío
│
▼
PedidoEnviado
Este ejemplo muestra también algo importante: una arquitectura basada en eventos no consiste únicamente en publicar un evento y ejecutar inmediatamente varias acciones independientes. Los propios consumidores pueden producir nuevos eventos, creando una cadena de acontecimientos. PedidoCreado puede provocar StockReservado; este puede provocar PedidoPreparado; posteriormente puede aparecer PedidoEnviado. El sistema empieza a describirse como una secuencia de hechos y reacciones en lugar de como una única cadena de llamadas entre servicios.
También aparecen aquí algunas de las dificultades que veremos más adelante. ¿Qué sucede si el consumidor de inventario recibe PedidoCreado pero falla antes de reservar el stock? ¿Qué ocurre si procesa el mensaje y, justo después, se produce un error antes de confirmar que lo ha procesado? ¿Puede recibir el mismo evento dos veces? ¿Qué ocurre si facturación tarda unos segundos más que inventario? Estas preguntas no son detalles secundarios: cuando sustituimos llamadas directas por acontecimientos asíncronos, estamos aceptando un modelo de comunicación con nuevas propiedades y, por tanto, nuevas responsabilidades de diseño.
Sincronía y asincronía: aceptar que no todo ocurre al mismo tiempo
Una de las características habituales de las arquitecturas basadas en eventos es el uso de comunicación asíncrona. En una llamada síncrona, el componente que realiza la petición espera normalmente una respuesta antes de continuar. En una comunicación asíncrona, el productor puede publicar el acontecimiento y continuar sin esperar a que todos los consumidores hayan terminado de reaccionar. Comunicación asíncrona en Wikipedia
Esto introduce una diferencia importante en la percepción del estado del sistema. El pedido puede haber sido creado a las 10:00:00, mientras que el sistema de analítica procesa el evento a las 10:00:01 y el sistema de notificaciones termina su trabajo a las 10:00:02. Desde el punto de vista del usuario, el pedido existe; desde el punto de vista de los diferentes consumidores, la información puede tardar un tiempo en propagarse. Esta situación se relaciona con la consistencia eventual: distintos componentes pueden observar temporalmente estados diferentes hasta que las actualizaciones terminan de propagarse. Consistencia eventual en Wikipedia
No debemos interpretar esto como un defecto automático. En muchos procesos es perfectamente aceptable que una notificación llegue unos instantes después de crear un pedido o que una métrica aparezca en el sistema analítico con cierto retraso. Lo importante es identificar qué partes del negocio necesitan una respuesta inmediata y cuáles pueden procesarse posteriormente. La arquitectura basada en eventos nos obliga a hacer explícita una decisión que en un sistema completamente síncrono puede quedar oculta: qué cosas necesitan ocurrir ahora y cuáles pueden ocurrir cuando el sistema esté preparado para procesarlas.
El precio de desacoplar: mensajes duplicados, fallos y reintentos
El desacoplamiento tiene un precio. Cuando los componentes dejan de comunicarse mediante llamadas directas y empiezan a trabajar de forma distribuida, aparecen nuevos escenarios de fallo. Un consumidor puede estar temporalmente caído, un mensaje puede necesitar ser reintentado o una operación puede completarse justo antes de que se produzca un error de comunicación.
Imaginemos que el consumidor de inventario recibe PedidoCreado, reserva correctamente el stock y guarda el cambio en su base de datos. Inmediatamente después, antes de confirmar al intermediario que el mensaje ha sido procesado, el proceso se detiene. Cuando vuelva a estar disponible, el mismo mensaje podría entregarse de nuevo. Si el consumidor no está preparado para este escenario, podría intentar reservar dos veces las mismas unidades.
Por eso aparece el concepto de idempotencia: una operación idempotente puede ejecutarse más de una vez con el mismo resultado efectivo que si se hubiera ejecutado una sola vez. Idempotencia en Wikipedia En una arquitectura basada en eventos, la posibilidad de recibir un mensaje más de una vez debe formar parte del diseño del consumidor, no tratarse como una anomalía imposible. Para conseguirlo podemos utilizar identificadores únicos de eventos, registros de mensajes procesados, operaciones idempotentes, reintentos controlados o mecanismos como las colas de mensajes no procesables.
Esto nos lleva también a cuestiones de observabilidad. Cuando una operación atraviesa varios componentes, necesitamos poder reconstruir qué ocurrió: qué evento se publicó, qué consumidor lo recibió, cuánto tardó, si falló, cuántas veces se reintentó y qué otros acontecimientos produjo. La arquitectura puede reducir el acoplamiento entre componentes, pero aumenta la importancia de disponer de trazabilidad y mecanismos adecuados para diagnosticar el comportamiento distribuido.
Eventos no significa utilizar una tecnología concreta
Llegados a este punto puede surgir una asociación casi automática: hablar de eventos parece llevarnos inmediatamente a tecnologías como Apache Kafka o RabbitMQ. Sin embargo, arquitectura, patrón y tecnología son niveles diferentes. Una arquitectura basada en eventos define una forma de organizar la comunicación; los patrones concretan cómo se relacionan los componentes; y una tecnología proporciona los mecanismos necesarios para implementar esas decisiones.
Además, Kafka y RabbitMQ no son simplemente dos nombres diferentes para la misma cosa. Kafka está especialmente orientado a escenarios de event streaming, donde los eventos pueden mantenerse en un registro distribuido y ser consumidos de acuerdo con diferentes modelos. RabbitMQ se centra en el intercambio de mensajes mediante un broker, con conceptos como colas, intercambios y enrutamiento. Ambos pueden utilizarse en sistemas basados en eventos, pero sus modelos y características hacen que determinadas soluciones encajen mejor con unas necesidades que con otras.
La decisión arquitectónica determina cómo queremos organizar la comunicación; el patrón determina cómo se relacionan productores y consumidores; y la tecnología proporciona los mecanismos concretos para llevar esa decisión a la práctica. Confundir estos tres niveles suele llevar a empezar por la herramienta antes de haber identificado el problema que queremos resolver.
Eventos y microservicios: relacionados, pero no equivalentes
Otra asociación frecuente es identificar automáticamente arquitectura basada en eventos con microservicios. Ambas ideas suelen aparecer juntas porque los eventos son una forma especialmente útil de comunicar servicios que queremos mantener desacoplados, pero describen problemas diferentes.
Los microservicios representan principalmente una forma de dividir y desplegar capacidades de una aplicación de manera independiente. La arquitectura basada en eventos, en cambio, describe una forma de comunicar acontecimientos entre componentes. Los microservicios describen principalmente una forma de dividir y desplegar capacidades, mientras que la arquitectura basada en eventos describe una forma de comunicar acontecimientos. Un monolito puede utilizar eventos internamente y una arquitectura de microservicios puede utilizar APIs síncronas basadas en HTTP sin emplear eventos.
Por tanto, no deberíamos introducir eventos simplemente porque estamos construyendo microservicios. La pregunta adecuada sigue siendo qué tipo de comunicación necesita nuestro sistema. Si una operación requiere una respuesta inmediata y existe una dependencia directa que resulta natural y controlada, una llamada síncrona puede ser perfectamente válida. Si, por el contrario, existen múltiples reacciones independientes ante un mismo acontecimiento, la comunicación mediante eventos puede representar mucho mejor el problema.
El sistema empieza a parecerse más a una red de acontecimientos
Cuando un sistema utiliza eventos de forma extensa, la arquitectura empieza a adquirir una forma diferente. Ya no tenemos solamente una serie de servicios conectados mediante llamadas, sino una red en la que los acontecimientos provocan nuevas reacciones y, en algunos casos, nuevos acontecimientos.
PedidoCreado
│
├──────────────► FacturaGenerada
│
├──────────────► NotificacionEnviada
│
└──────────────► StockReservado
│
▼
PedidoPreparado
│
▼
PedidoEnviado
Esto puede resultar muy potente, pero también hace que el comportamiento global sea más difícil de seguir. En una llamada directa podemos abrir el código y observar que A llama a B y B devuelve una respuesta. En una arquitectura distribuida mediante eventos, la relación puede ser más indirecta: A publica un acontecimiento, B reacciona y produce otro, C consume ese nuevo evento y genera otro más. Este estilo de coordinación suele relacionarse con la coreografía (choreography), en la que los participantes reaccionan ante acontecimientos sin existir necesariamente un componente central que dirija toda la secuencia.
A medida que crece esta red, la observabilidad deja de ser una preocupación secundaria. Los identificadores de correlación, los registros estructurados, las métricas, los trazados distribuidos y una buena documentación de los contratos de eventos se convierten en herramientas fundamentales para comprender qué está ocurriendo. El desacoplamiento reduce determinadas dependencias, pero aumenta la necesidad de comprender el comportamiento global del sistema.
Cuándo tiene sentido utilizar una arquitectura basada en eventos
No todas las aplicaciones necesitan una arquitectura basada en eventos. Si tenemos una aplicación pequeña, con pocas dependencias y operaciones que necesitan respuestas inmediatas, introducir un broker, consumidores independientes, reintentos y procesamiento asíncrono puede añadir más complejidad de la que resuelve. La arquitectura debe responder a las necesidades del sistema, no convertirse en un objetivo por sí misma.
Los eventos empiezan a resultar especialmente interesantes cuando un mismo acontecimiento puede provocar varias reacciones independientes, cuando esas reacciones no necesitan completarse de forma síncrona, cuando queremos reducir dependencias directas entre componentes o cuando diferentes sistemas necesitan reaccionar ante información producida por otro. También resultan útiles cuando prevemos que aparecerán nuevos consumidores a lo largo del tiempo y queremos evitar que cada incorporación obligue a modificar el componente que origina el acontecimiento.
Una buena pregunta arquitectónica no sería «¿deberíamos usar Kafka?», sino «¿qué dependencias directas queremos evitar y qué acontecimientos necesitan conocer otros componentes?». A partir de ahí podremos determinar si necesitamos eventos, qué eventos tienen significado real, qué consumidores existen, qué garantías de entrega necesitamos y qué tecnología resulta apropiada.
Una forma diferente de pensar la comunicación
La arquitectura basada en eventos no consiste simplemente en sustituir llamadas HTTP por mensajes. Su verdadera aportación está en cambiar la forma en que pensamos sobre la comunicación entre las partes de un sistema. En lugar de organizar toda interacción alrededor de la pregunta «¿a quién tengo que llamar para que haga esto?», podemos empezar a pensar también en términos de «¿qué ha ocurrido y quién necesita saberlo?».
Ese cambio permite construir sistemas en los que los componentes tienen responsabilidades más independientes y pueden reaccionar ante acontecimientos sin conocerse directamente. Pero esa independencia tiene un coste: aparecen contratos de eventos, procesamiento asíncrono, consistencia eventual en determinados escenarios, reintentos, duplicados y una mayor necesidad de observabilidad. No estamos eliminando la complejidad de la comunicación; estamos trasladando parte de esa complejidad desde las dependencias directas hacia la gestión explícita de acontecimientos y sus consecuencias.
Al final, la cuestión no es utilizar eventos porque sean una tecnología moderna ni porque formen parte habitual de determinadas arquitecturas de microservicios. La cuestión es reconocer aquellos lugares donde un sistema no necesita decirle directamente a otro qué hacer, sino comunicar que algo importante ha sucedido y permitir que las partes interesadas reaccionen de manera independiente. Cuando ese modelo representa mejor el negocio y las relaciones entre componentes, el sistema deja de organizarse exclusivamente alrededor de llamadas y empieza a organizarse alrededor de acontecimientos.