Patrón Event Sourcing

Bookings

En lugar de almacenar simplemente el estado actual de los datos de un dominio, use un almacén de solo anexar para registrar toda la serie de acciones realizadas en los datos. El almacén actúa como el sistema de registro y puede usarse para materializar los objetos de dominio. Esto puede simplificar las tareas en los dominios complejos, al evitar la necesidad de sincronizar el modelo de datos y el dominio empresarial, al tiempo que mejora el rendimiento, la escalabilidad y la capacidad de respuesta. También proporciona coherencia a los datos transaccionales y conserva trazas de auditoría e historiales completos que permiten acciones de compensación.

Contexto y problema

La mayoría de las aplicaciones trabajan con datos; el enfoque típico es que la aplicación mantenga el estado actual de los datos mediante su actualización conforme los usuarios trabajan con ellos. Por ejemplo, en el modelo tradicional de creación, lectura, actualización y eliminación (CRUD), un proceso de datos típico consiste en leer datos del almacén, realizar algunas modificaciones en ellos y actualizar su estado actual con los nuevos valores (a menudo mediante transacciones que bloquean los datos).

El enfoque CRUD tiene algunas limitaciones:

  • Los sistemas CRUD realizan operaciones de actualización directamente en un almacén de datos, lo que puede ralentizar el rendimiento y la capacidad de respuesta, así como limitar la escalabilidad, a causa de la sobrecarga de procesamiento que requiere.

  • En un dominio de colaboración con muchos usuarios simultáneos, los conflictos de actualización de datos son más probables, dado que las operaciones de actualización tienen lugar en un solo elemento de datos.

  • A menos que exista otro mecanismo de auditoría que registre los detalles de cada operación en un registro independiente, el historial se pierde.

Solución

El patrón Event Sourcing define un enfoque para controlar las operaciones basado en una secuencia de eventos, cada uno de los cuales se registra en un almacén de solo anexar. El código de la aplicación envía una serie de eventos que imperativamente describen cada acción que se ha producido en los datos del almacén de eventos, donde se conservan. Cada evento representa un conjunto de cambios en los datos (como AddedItemToOrder).

Los eventos se conservan en un almacén de eventos que actúa como sistema de registro (origen de datos acreditado) del estado actual de los datos. Normalmente, el almacén de eventos publica estos eventos para que los consumidores pueden recibir notificaciones y controlarlos si lo necesitan. Los consumidores podrían, por ejemplo, iniciar tareas que se aplican las operaciones en los eventos a otros sistemas o realizar cualquier acción asociada que se necesita para completar la operación. Tenga en cuenta que el código de aplicación que genera los eventos es independiente de los sistemas que se suscriben a los eventos.

Los usos típicos de los eventos publicados por el almacén de eventos son el mantenimiento de vistas materializadas de entidades según las cambian las acciones de la aplicación y la integración con sistemas externos. Por ejemplo, un sistema puede mantener una vista materializada de todos los pedidos de cliente que se usa para rellenar elementos de la interfaz de usuario. La aplicación agrega nuevos pedidos, agrega o quita elementos del pedido y agrega información de envío. Los eventos que describen estos cambios se pueden controlar y utilizar para actualizar la vista materializada.

En cualquier momento, las aplicaciones pueden leer el historial de eventos. Después, se puede usar para materializar el estado actual de una entidad, para lo que es preciso reproducir todos los eventos relacionados con esa entidad y consumirlos. Este proceso puede producirse a petición para materializar un objeto de dominio al controlar una solicitud. O bien, el proceso se procesa a través de una tarea programada para que se puede almacenar el estado de la entidad como vista materializada para admitir la capa de presentación.

La ilustración muestra una introducción al patrón, incluidas algunas de las opciones de uso del flujo de eventos, como la creación de una vista materializada, la integración de eventos con aplicaciones y sistemas externos y la reproducción de eventos para crear proyecciones del estado actual de entidades específicas.

Introducción y ejemplo del patrón Event Sourcing

El patrón Event Sourcing proporciona las siguientes ventajas:

  • Los eventos son inmutables y pueden almacenarse mediante una operación de solo anexar. La interfaz de usuario, el flujo de trabajo o el proceso que iniciaran un evento pueden continuar y las tareas que controlan los eventos se pueden ejecutar en segundo plano. Este proceso, junto con el hecho de que no hay ningún contención durante el procesamiento de transacciones, puede mejorar considerablemente el rendimiento y la escalabilidad de las aplicaciones, especialmente en el nivel de presentación o en la interfaz de usuario.

  • Los eventos son objetos simples que describen alguna acción que se ha producido, junto con los datos asociados necesarios para describir la acción que representa el evento. Los eventos no actualizan directamente un almacén de datos. Simplemente se registran para el control en el momento adecuado. El uso de eventos puede simplificar la implementación y la administración.

  • Los eventos suelen tener significado para un experto de dominio, mientras que los errores de coincidencia de impedancia relacional de objetos pueden dificultar la comprensión de las tablas de base de datos complejas. Las tablas son construcciones artificiales que representan el estado actual del sistema, no los eventos que se han producido.

  • Event Sourcing puede ayudar a impedir que las actualizaciones simultáneas causen conflictos, ya que evita la necesidad de actualizar directamente los objetos en el almacén de datos. Sin embargo, el modelo de dominio debe diseñarse aún para protegerlo de las solicitudes que puedan generar incoherencias.

  • El almacenamiento de eventos de solo anexar proporciona un registro de auditoría que se puede usar para supervisar las acciones realizadas en un almacén de datos. Puede volver a generar el estado actual como vistas materializadas o proyecciones mediante la reproducción de los eventos en cualquier momento, y puede resultar de ayuda si se realizan pruebas o se depura el sistema. Además, el requisito de usar eventos de compensación para cancelar los cambios puede proporcionar un historial de los cambios que se han invertido. Esta funcionalidad no se usaría si el modelo almacenara el estado actual. La lista de eventos también se puede utilizar para analizar el rendimiento de la aplicación y detectar las tendencias de comportamiento de los usuarios. O bien, se puede usar para obtener otra información empresarial de utilidad.

  • El almacén de eventos genera eventos y las tareas realizan operaciones en respuesta a esos eventos. Esta separación de las tareas de los eventos proporciona flexibilidad y extensibilidad. Las tareas conocen el tipo de evento y los datos del evento, pero no la operación que lo desencadenó. Además, varias tareas pueden controlar el mismo evento. Esto facilita la integración con otros servicios y sistemas que solo escuchen los nuevos eventos que genere el almacén de eventos. Sin embargo, los eventos de Event Sourcing tienden a ser de muy bajo nivel, por lo que podría ser necesaria la creación de eventos de integración específicos en su lugar.

Event Sourcing suele combinarse con el patrón CQRS, ya que realiza las tareas de administración de datos en respuesta a los eventos y materializa las vistas de los eventos almacenados.

Problemas y consideraciones

Tenga en cuenta los puntos siguientes al decidir cómo implementar este patrón:

El sistema solo será coherente al crear vistas materializadas o proyecciones de los datos mediante la reproducción de eventos. Se produce cierto retraso entre que una aplicación agrega eventos al almacén tras administrar una solicitud, que los eventos se publican y que los consumidores los controlan. Durante este período pueden haber llegado al almacén eventos nuevos que describan más cambios en las entidades. El sistema debería diseñarse para tener en cuenta la coherencia final en estos escenarios.

Nota

Consulte Data Consistency Primer(Manual básico de coherencia de datos) para información sobre la coherencia final.

El almacén de eventos es la fuente permanente de información, por lo que los datos de los eventos no deben actualizarse. Es la única manera de actualizar una entidad para deshacer un cambio es agregar un evento de compensación al almacén de eventos. Si necesita cambiar el formato (en lugar de los propios datos) de los eventos persistentes, quizás durante una migración, puede ser difícil combinar los eventos existentes en el almacén con la nueva versión. Podría ser necesario recorrer en iteración todos los eventos con cambios para que sean compatibles con el nuevo formato o agregar nuevos eventos que utilicen el nuevo formato. Considere el uso de una marca de versión para cada versión del esquema de evento para mantener el formato antiguo y el nuevo.

En el almacén de eventos pueden guardar eventos las aplicaciones multiproceso y varias instancias de aplicaciones. La coherencia de los eventos del almacén es muy importante, ya que supone el orden de los eventos que afectan a una entidad específica (el orden en que se producen los cambios en una entidad afecta a su estado actual). Agregar una marca de tiempo para cada evento puede ayudar a evitar problemas. Otra práctica común consiste en anotar cada evento derivado de una solicitud con un identificador incremental. Si dos acciones intentan agregar eventos a la misma entidad al mismo tiempo, el almacén de eventos puede rechazar uno cuyo identificador de entidad existente coincida con el identificador de evento.

No hay ningún enfoque estándar ni mecanismos existentes como consultas SQL para leer los eventos y obtener información. Los únicos datos que se pueden extraer son un flujo de eventos con un identificador de evento como criterio. El identificador de evento normalmente se asigna a entidades individuales. Se puede determinar el estado actual de una entidad solo mediante la reproducción de todos los eventos relacionados con ella en comparación con el estado original de la entidad.

La longitud de los flujos de eventos afecta a la administración y la actualización del sistema. Si son grandes, considere la posibilidad de crear instantáneas a intervalos específicos, como de un número determinado de eventos. El estado actual de la entidad puede obtenerse de la instantánea y al reproducir los eventos que se produjeran después de ese momento. Para más información sobre la creación de instantáneas de datos, consulte Replicación de instantáneas principal-subordinado.

Aunque Event Sourcing reduce la posibilidad de conflicto entre actualizaciones de los datos, la aplicación sigue teniendo que procesar las incoherencias derivadas de la coherencia final y la falta de transacciones. Por ejemplo, un evento que indica una reducción en el inventario estándar puede llegar al almacén de datos mientras se realiza un pedido de ese elemento. Esta situación requeriría una conciliación de ambas operaciones mediante un aviso al cliente o la creación de un pedido en espera.

La publicación de eventos podría ser al menos una vez, por lo que los consumidores de eventos deben ser idempotentes. No debe volver a aplicar la actualización descrita en un evento si este se controla más de una vez. Varias instancias de un consumidor pueden mantener y agregar la propiedad de una entidad, como el número total de pedidos realizados. Solo una debe incrementar correctamente el agregado cuando se produce un evento de pedido realizado. Aunque esto no es una característica clave del aprovisionamiento de eventos, es la decisión de implementación habitual.

El almacenamiento de eventos seleccionado debe admitir la carga de eventos generada por la aplicación.
Tenga en cuenta los escenarios en los que el procesamiento de un evento implique la creación de uno o varios eventos nuevos, ya que esto podría provocar un bucle infinito.

Cuándo usar este patrón

Use este patrón en los escenarios siguientes:

  • Si desea capturar la intención, el propósito o el motivo en los datos. Por ejemplo, los cambios en una entidad de cliente se pueden capturar como una serie de tipos de evento específicos como Cambio de domicilio, Cierre de cuenta o Fallecimiento.

  • Cuando es vital reducir o evitar por completo la aparición de actualizaciones de datos en conflicto.

  • Si desea registrar los eventos que se produzcan, con el fin de reproducirlos para restaurar el estado de un sistema, revertir cambios o mantener un historial y un registro de auditoría. Por ejemplo, cuando una tarea implica varios pasos, es posible que tenga que ejecutar acciones para revertir las actualizaciones y, después, reproducir algunos pasos para devolver los datos a un estado coherente.

  • Cuando se usan eventos. Es una característica natural de la operación de la aplicación y requiere un pequeño esfuerzo extraordinario de implementación o desarrollo.

  • Cuando necesite desacoplar el proceso de entrada o actualización de datos de las tareas necesarias para aplicar estas acciones. Este cambio se puede realizar para mejorar el rendimiento de la interfaz de usuario o para distribuir eventos a otros agentes de escucha que realicen acciones cuando se producen los eventos. Por ejemplo, puede integrar un sistema de nóminas en un sitio web de envío de gastos. Los eventos que genera el almacén de eventos en respuesta a las actualizaciones de datos realizadas en el sitio web los consumirían tanto el sistema de nóminas como el sistema de nóminas.

  • Si desea flexibilidad para poder cambiar el formato de los modelos materializados y los datos de la entidad si cambian los requisitos o cuando tenga que adaptar un modelo de lectura o las vistas que exponen los datos (cuando se utiliza con CQRS).

  • Cuando se usa con CQRS y la coherencia final es aceptable mientras se actualiza un modelo de lectura o si el impacto sobre el rendimiento de rehidratar las entidades y los datos de un flujo de eventos es aceptable.

Este patrón podría no ser útil en las siguientes situaciones:

  • Dominios pequeños o simples, sistemas con poca o ninguna lógica de negocios o sistemas sin dominio que funcionan bien naturalmente con mecanismos de administración de datos CRUD tradicionales.

  • Sistemas en los que se necesite coherencia y actualizaciones en las vistas de los datos en tiempo real.

  • Sistemas en los que no se necesiten registros de auditoría, historial y funcionalidades para revertir y reproducir acciones.

  • Sistemas en los que donde la cantidad de actualizaciones en conflicto en los datos subyacentes es baja. Por ejemplo, los sistemas que fundamentalmente agregan datos en lugar de actualizarlos.

Diseño de cargas de trabajo

El arquitecto debe evaluar cómo se puede usar el patrón de abastecimiento de eventos en el diseño de su carga de trabajo para abordar los objetivos y principios tratados en los pilares del Marco de la Well-Architected de Azure. Por ejemplo:

Fundamento Cómo apoya este patrón los objetivos de los pilares
Las decisiones de diseño de la fiabilidad ayudan a que la carga de trabajo sea resistente a los errores y a garantizar que se recupere a un estado de pleno funcionamiento después de que se produzca un error. Debido a la captura de un historial de cambios en el proceso de negocio complejo, puede facilitar la reconstrucción del estado si necesita recuperar los almacenes de estado.

- RE:06 Creación de particiones de datos
- RE:09 Recuperación ante desastres
La eficiencia del rendimiento ayuda a que la carga de trabajo satisfaga eficazmente las demandas mediante optimizaciones en el escalado, los datos y el código. Este patrón, normalmente combinado con CQRS, un diseño de dominio apropiado y una toma de instantáneas estratégica, puede mejorar el rendimiento de la carga de trabajo debido a las operaciones atómicas de solo anexión y a la evitación del bloqueo de la base de datos para escrituras y lecturas.

- PE:08 Rendimiento de datos

Al igual que con cualquier decisión de diseño, hay que tener en cuenta las ventajas y desventajas con respecto a los objetivos de los otros pilares que podrían introducirse con este patrón.

Ejemplo

Un sistema de administración de conferencias debe realizar un seguimiento del número de reservas completadas para una conferencia. De esta forma podrá comprobar si hay plazas disponibles cuando un posible asistente intente realizar una reserva. El sistema puede almacenar el número total de reservas para una conferencia de al menos dos formas:

  • Almacenar la información sobre el número total de reservas como una entidad independiente en una base de datos con la información de reserva. Como las reservas se realizan o se cancelan, el sistema puede incrementar o disminuir este número según corresponda. En teoría, este enfoque es sencillo, pero puede causar problemas de escalabilidad si un gran número de asistentes intenta reservar plaza en un tiempo breve. Por ejemplo, en el último día o dos antes del cierre del período de reserva.

  • El sistema podría almacenar información acerca de las reservas y cancelaciones como eventos que se encuentran en un almacén de eventos. A continuación, calcularía el número de plazas disponibles mediante la reproducción de estos eventos. Este enfoque puede ser más escalable debido a la inmutabilidad de los eventos. El sistema solo necesita leer los datos del almacén de eventos o anexarlos al almacén de eventos. La información de reservas y cancelaciones de eventos nunca se modifica.

El siguiente diagrama ilustra cómo se podría implementar con Event Sourcing el subsistema de reserva plazas del sistema de administración de conferencias.

Captura de información sobre las reservas de plaza en un sistema de administración de conferencias con Event Sourcing

La secuencia de acciones para reservar dos plazas es la siguiente:

  1. La interfaz de usuario emite un comando para reservar plaza a dos asistentes. El comando lo controla un controlador de comandos independiente. Se separa una parte de la lógica de la interfaz de usuario y es responsable de controlar las solicitudes que se publican como comandos.

  2. Al consultar los eventos que se describen las reservas y las cancelaciones se crea un agregado que contiene información sobre todas las reservas de la conferencia. Este agregado se denomina SeatAvailability y se encuentra dentro de un modelo de dominio que expone métodos para consultar y modificar sus datos.

    Algunas optimizaciones que tener en cuenta son el uso de instantáneas (para no tener que consultar y reproducir la lista completa de eventos para obtener el estado actual del agregado) y la conservación de una copia en caché del agregado en la memoria.

  3. El controlador de comandos invoca un método expuesto por el modelo de dominio para realizar las reservas.

  4. El agregado SeatAvailability registra un evento que contiene el número de plazas que se han reservado. La próxima vez que el agregado aplique eventos, todas las reservas se utilizarán para calcular el número de plazas restantes.

  5. El sistema anexa el nuevo evento a la lista del almacén de eventos.

Si un usuario cancela una plaza, el sistema sigue un proceso similar, salvo que el controlador de comandos emite un comando que genera un evento de cancelación de plaza y lo anexa al almacén de eventos.

Además de proporcionar un ámbito más escalabilidad, un almacén de eventos también proporciona un historial completo (o traza de auditoría) de las reservas y las cancelaciones de una conferencia. Los eventos del almacén son el registro exacto. No es necesario conservar los agregados de otra manera, ya que el sistema puede reproducir fácilmente los eventos y restaurar el estado a cualquier punto en el tiempo.

Más información acerca de este ejemplo en el artículo de introducción Event Sourcing.

Pasos siguientes

Los patrones y las directrices siguientes también pueden ser importantes a la hora de implementar este modelo:

  • Patrón Command and Query Responsibility Segregation (CQRS). El almacén de escritura que proporciona el origen de información permanente para una implementación de CQRS a menudo se basa en una implementación del patrón Event Sourcing. Describe cómo se segregan las operaciones de lectura de las de actualización de datos mediante interfaces independientes en una aplicación.

  • Patrón Materialized View. Normalmente, el almacén de datos que se utiliza en un sistema basado en el aprovisionamiento de eventos no se adapta perfectamente para una consulta eficaz. En su lugar, un enfoque común consiste en generar vistas rellenadas previamente con los datos a intervalos regulares o cuando los datos cambian.

  • Patrón Compensating Transaction. Los datos existentes en un almacén de aprovisionamiento de eventos no se actualizan. En su lugar, se agregan entradas nuevas de transición del estado de las entidades a los nuevos valores. Para invertir un cambio, se usan las entradas de compensación, puesto que no es posible revertir el cambio anterior. Describe cómo deshacer el trabajo que realizó una operación anterior.