Compartir a través de


Patrón Event Sourcing

Azure

En lugar de almacenar solo el estado actual de los datos en una base de datos relacional, almacene la serie completa de acciones realizadas en un objeto en un almacén de solo anexión. El almacén actúa como el sistema de registro y puede usarse para materializar los objetos de dominio. Este enfoque puede mejorar el rendimiento, la escalabilidad y la auditabilidad en sistemas complejos.

Importante

El aprovisionamiento de eventos es un patrón complejo que impregna toda la arquitectura e introduce inconvenientes para lograr un mayor rendimiento, escalabilidad y auditabilidad. Una vez que el sistema se convierte en un sistema de aprovisionamiento de eventos, todas las decisiones de diseño futuras están restringidas por el hecho de que se trata de un sistema de aprovisionamiento de eventos. Hay un alto costo para migrar a o desde un sistema de aprovisionamiento de eventos. Este patrón es más adecuado para los sistemas en los que el rendimiento y la escalabilidad son los requisitos principales. La complejidad que el aprovisionamiento de eventos agrega a un sistema no está justificada para la mayoría de los sistemas.

Contexto y problema

La mayoría de las aplicaciones funcionan con datos y el enfoque típico es que la aplicación almacene el estado más reciente de los datos en una base de datos relacional, insertando o actualizando datos según sea necesario. Por ejemplo, en el modelo tradicional de creación, lectura, actualización y eliminación (CRUD), un proceso de datos típico es leer datos del almacén, realizar algunas modificaciones y actualizar el estado actual de los datos con los nuevos valores, a menudo mediante transacciones que bloquean los datos.

El enfoque CRUD es sencillo y rápido para la mayoría de los escenarios. Sin embargo, en sistemas de alta carga, este enfoque tiene algunos desafíos:

  • Rendimiento: a medida que el sistema se escala, el rendimiento se degradará debido a la contención de recursos y problemas de bloqueo.

  • Escalabilidad: los sistemas CRUD son sincrónicos y los bloques de operaciones de datos en las actualizaciones. Esto puede provocar cuellos de botella y una mayor latencia cuando el sistema está bajo carga.

  • Auditabilidad: los sistemas CRUD solo almacenan el estado más reciente de los datos. A menos que haya un mecanismo de auditoría que registre los detalles de cada operación en un registro independiente, se pierde el historial.

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 aplicación genera eventos que describen imperativamente la acción realizada en el objeto . Por lo general, los eventos se envían a una cola en la que un proceso independiente, un controlador de eventos, escucha la cola y conserva los eventos en un almacén de eventos. Cada evento representa un cambio lógico en el objeto, como AddedItemToOrder o OrderCanceled.

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. Los controladores de eventos adicionales pueden escuchar eventos que les interesan y tomar una acción adecuada. 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.

En cualquier momento, las aplicaciones pueden leer el historial de eventos. A continuación, puede usar los eventos para materializar el estado actual de una entidad reproduciendo y usando todos los eventos relacionados con esa entidad. Este proceso puede producirse a petición para materializar un objeto de dominio al controlar una solicitud.

Dado que es relativamente caro leer y reproducir eventos, las aplicaciones suelen implementar vistas materializadas, proyecciones de solo lectura del almacén de eventos que están optimizadas para realizar consultas. Por ejemplo, un sistema puede mantener una vista materializada de todos los pedidos de cliente que se usan para rellenar la interfaz de usuario. A medida que la aplicación agrega nuevos pedidos, agrega o quita elementos en el pedido, o agrega información de envío, se generan eventos y un controlador actualiza la vista materializada.

En la ilustración se muestra una visión general del patrón, incluidas algunas implementaciones típicas con el patrón, como el uso de una cola, un almacén de solo lectura, 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

Flujo de trabajo

A continuación se describe un flujo de trabajo típico para este patrón:

  1. La capa de presentación llama a un objeto responsable de leer desde un almacén de solo lectura. Los datos devueltos se usan para rellenar la interfaz de usuario.
  2. La capa de presentación llama a los controladores de comandos para realizar acciones como crear un carro o agregar un elemento al carro.
  3. El controlador de comandos llama al almacén de eventos para obtener los eventos históricos de la entidad. Por ejemplo, puede recuperar todos los eventos de carro. Esos eventos se reproducen en el objeto para materializar el estado actual de la entidad, antes de que se realice cualquier acción.
  4. La lógica de negocios se ejecuta y se generan eventos. En la mayoría de las implementaciones, los eventos se insertan en una cola o tema para desacoplar los productores de eventos y los consumidores de eventos.
  5. Los controladores de eventos escuchan eventos que les interesan y realizan la acción adecuada para ese controlador. Algunas acciones típicas del controlador de eventos son:
    1. Escritura de los eventos en el almacén de eventos
    2. Actualización de un almacén de solo lectura optimizado para consultas
    3. Integración con sistemas externos

Ventajas de patrones

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, combinado con el hecho de que no hay contención durante el procesamiento de transacciones, puede mejorar enormemente el rendimiento y la escalabilidad de las aplicaciones, especialmente para la capa de presentación.

  • 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.

  • Los controladores de comandos generan 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.

El aprovisionamiento de eventos se combina normalmente con el patrón CQRS realizando las tareas de administración de datos en respuesta a los eventos y materializando vistas de los eventos almacenados.

Problemas y consideraciones

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

  • Coherencia final : el sistema solo será coherente al crear vistas materializadas o generar proyecciones de 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. Los clientes deben estar de acuerdo con el hecho de que los datos sean coherentes finalmente y el sistema debe 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.

  • Eventos de control de versiones: el almacén de eventos es el origen permanente de información, por lo que los datos del evento nunca se deben actualizar. La única manera de actualizar una entidad o deshacer un cambio es agregar un evento de compensación al almacén de eventos. Si el esquema (en lugar de los datos) de los eventos persistentes debe cambiar, quizás durante una migración, puede ser difícil combinar eventos existentes en el almacén con la nueva versión. La aplicación tendrá que admitir cambios en las estructuras de eventos. Esto se puede llevar a cabo de varias maneras.

    • Asegúrese de que los controladores de eventos admiten todas las versiones de eventos. Esto puede ser un desafío para mantener y probar. Esto requiere implementar una marca de versión en cada versión del esquema de eventos para mantener los formatos de evento antiguos y nuevos.
    • Implemente un controlador de eventos para controlar versiones de eventos específicas. Esto puede ser un desafío de mantenimiento en que es posible que los cambios de corrección de errores se tengan que realizar en varios controladores. Esto requiere implementar una marca de versión en cada versión del esquema de eventos para mantener los formatos de evento antiguos y nuevos.
    • Actualice los eventos históricos al nuevo esquema cuando se implemente un nuevo esquema. Esto interrumpe la inmutabilidad de los eventos.
  • Ordenación de eventos: las aplicaciones multiproceso y varias instancias de aplicaciones pueden almacenar eventos en el almacén de eventos. 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.

  • Consulta de eventos : no hay ningún enfoque estándar o mecanismos existentes, como consultas SQL, para leer los eventos para 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.

  • Costo de volver a crear el estado de las entidades : la longitud de cada flujo de eventos afecta a la administración y 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.

  • Conflictos : aunque el aprovisionamiento de eventos minimice la posibilidad de que se produzcan conflictos de actualizaciones en los datos, la aplicación todavía debe ser capaz de tratar con incoherencias que resultan 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.

  • Necesidad de idempotency : la publicación de eventos puede ser al menos una vez, por lo que los consumidores de los 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.

  • Lógica circular : tenga en cuenta los escenarios en los que el procesamiento de un evento implica la creación de uno o varios eventos nuevos, ya que esto puede 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:

  • Aplicaciones que no requieren hiperespase o rendimiento.

  • 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 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. Una entidad que contiene información sobre todas las reservas de la conferencia se construye consultando los eventos que describen las reservas y cancelaciones. Esta entidad se denomina SeatAvailability, y se encuentra dentro de un modelo de dominio que expone métodos para consultar y modificar los datos de la entidad.

    Algunas optimizaciones que se deben tener en cuenta son el uso de instantáneas (por lo que no es necesario consultar y reproducir la lista completa de eventos para obtener el estado actual de la entidad) y mantener una copia almacenada en caché de la entidad en memoria.

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

  4. La SeatAvailability entidad genera un evento que contiene el número de puestos reservados. La próxima vez que la entidad aplique eventos, todas las reservas se usarán para calcular cuántos puestos permanecen.

  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.

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.