Suministro de eventos, reproducción y versiones

8

Estoy diseñando un sistema que utiliza Event Sourcing, CQRS y microservicios. Tengo que entender que este no es un patrón poco común. Una característica clave del servicio debe ser la capacidad de rehidratarse / restaurarse de un sistema de registro. Los microservicios producirán comandos y consultas en un MQ (Kafka). Otros microservicios responderán (eventos). Los comandos y consultas se mantendrán en S3 con el propósito de auditar y restaurar.

El proceso de pensamiento actual era que, con el fin de restaurar el sistema, podríamos extraer el registro de eventos de S3 y simplemente enviarlo a Kafka.

Sin embargo, esto no reconoce cambios en productores y consumidores a lo largo del tiempo. El control de versiones en el nivel de comando / consulta parece ir de alguna manera hacia la solución del problema, pero no puedo entender cómo versionar a los consumidores de modo que pueda aplicar que cuando un comando, durante una restauración, se recibe y procesa, es exactamente el mismo [versión del] código que realiza el procesamiento, ya que fue la primera vez que se recibió el comando.

¿Hay algún patrón que pueda usar para resolver esto? ¿Alguien sabe de otros sistemas que anuncian esta característica?

EDITAR: Agregar un ejemplo.

Un 'comprador' envía una 'pregunta' a un 'vendedor' en mi sitio de subastas. El flujo tiene el siguiente aspecto: UI -> Web App: POST /question {:text text :to seller-id :from user-id} Web App -> MQ: SEND {:command send-question :args [text seller-id user-id]} MQ -< Audit: <command + args appended to log in S3> MQ -< Questions service: - Record question in DB - Email seller 'You have a question'

Ahora, como resultado de un nuevo requisito comercial, ajusto el consumidor del 'Servicio de preguntas' para que continúe contando todas las preguntas no leídas. El esquema de base de datos ha cambiado. Hasta ahora no teníamos ni idea de si el vendedor leía o no una pregunta. La última línea se convierte en:

MQ -< Questions service: - Record question in DB - Email seller 'You have a question' - Increment 'unread questions count'

Dos comandos son problemas, uno antes del cambio, uno después del cambio. El 'conteo de preguntas no leídas' es igual a 1.

El sistema se bloquea. Restauramos al reproducir los comandos a través del nuevo código. Al final de la restauración, nuestro 'conteo de preguntas no leídas' es igual a 2. Aunque, en este ejemplo artificial, el resultado no es una catástrofe, el estado que ha sido restaurado no es lo que era antes.

Antony Woods
fuente
La pregunta parece mezclar un par de preocupaciones. El abastecimiento de eventos es una estrategia arquitectónica para tratar con sistemas donde se están produciendo muchos cambios en los datos subyacentes. El control de versiones de los DTO y las copias de seguridad y los datos es otra cuestión. El abastecimiento de eventos no está diseñado específicamente para brindarle la capacidad de restauración completa: para eso, debe tener una estrategia específica.
theMayer
Quizás la transmisión de eventos puede resolver su problema: blog.trifork.com/2012/04/17/…
Songo
@ rmayer06 ¡Creo que un ejemplo es lo que busco!
Antony Woods

Respuestas:

16

Primero, es importante comprender y poder aprovechar la diferencia entre Comandos y Eventos.

Como esta pregunta señala sucintamente, los comandos son cosas que nos gustaría que sucedan, y los eventos son cosas que ya han sucedido. Un comando no necesariamente resulta en un evento significativo en el sistema, pero generalmente lo hace. Por ejemplo, un send messagecomando puede ser rechazado, en cuyo caso no ocurre ningún evento (por lo general, un error no se consideraría un evento en este sentido, aunque aún podemos elegir registrarlo en un registro de diagnóstico). Ahora, si send messagese acepta el comando, message sentse produce el evento y los detalles del evento podrían describir al remitente, al receptor y al contenido.

Cuando hablamos sobre el estado del sistema, en realidad estamos discutiendo no una culminación de comandos, sino de eventos. Solo los eventos pueden causar un cambio de estado en el sistema. Para sacar un ejemplo de la vida, supongamos que voy al supermercado local Publix y compro un boleto de lotería de Florida. El comando era "Comprar boleto" y el evento era "Boleto emitido". Mi siguiente comando entonces es a la lotería para dibujar mis números para el PowerBall. La lotería va a ignorar mi comando (pero no tengo conocimiento), y el evento "Números de PowerBall elegidos" tiene lugar independientemente de mis deseos. Si mis números coinciden, me ocurre el evento "Jackpot ganó" (y creo que se escuchó mi comando). Si no, me doy cuenta de que mi comando fue ignorado.

Desde una perspectiva histórica, la lotería solo está interesada en un subconjunto de eventos. A la lotería solo le importa que (a) se haya emitido un boleto, (b) se hayan elegido los números y (c) se haya ganado el premio mayor. Esos son los artículos de interés. El acto de comprar el boleto, querer ganar, etc., son irrelevantes, como es lo que hago con mi boleto después de perder. Si bien el mundo real cambia para los eventos mundanos, solo necesitamos registrar aquellos eventos que son importantes para nuestro sistema.

En teoría, bajo una técnica de obtención de eventos, se puede reproducir una secuencia de eventos desde el principio de los tiempos para llegar al estado actual. Esto se basa en el supuesto de que las condiciones subyacentes del sistema son constantes y deterministas. Sin embargo, estos supuestos no son válidos en muchos sistemas. Los datos asociados con un evento, así como los tipos de eventos que nos interesan, pueden cambiar a medida que evoluciona nuestro software. Además, puede ser computacionalmente costoso volver a calcular el estado actual en respuesta a cada consulta. Por esta razón, las instantáneas del estado del sistema a menudo se toman para representar puntos conocidos en el tiempo, a los cuales se pueden agregar los eventos más recientes.

Si bien aún es posible reproducir una secuencia de eventos en varias versiones, es probable que la cantidad de esfuerzo humano involucrado en hacerlo sea prohibitiva. A menos que haya una razón justificable para diseñar esa capacidad en el sistema, es mejor que construya su sistema para utilizar instantáneas.

Ejemplo en cuestión

En el ejemplo dado en la pregunta, la arquitectura no está realmente basada en eventos; Está basado en comandos. La reproducción de comandos crea el estado del sistema. Este es un antipatrón y debe repararse. En cambio, los eventos principales son:

  • El comprador hace una pregunta
  • El vendedor responde a la pregunta

Cada uno de estos eventos puede "reproducirse" para dar el estado actual. Por ejemplo, en el acto de hacer una pregunta, el comportamiento del sistema podría ser enviar un correo electrónico al vendedor e incrementar el unanswered questioncontador. Este comportamiento puede ser cambiado; sin embargo, el hecho de que se hizo la pregunta no lo hace. Del mismo modo, el sistema puede disminuir el unanswered questioncontador cuando el vendedor responde. Este comportamiento es modificable, pero el hecho de que el vendedor respondió no lo es.

La mayoría de los sistemas de abastecimiento de eventos calcularían dinámicamente el recuento de preguntas sin respuesta al reproducir el flujo de eventos específico en respuesta a una consulta.

theMayer
fuente
Esta es una gran respuesta, gracias @ rmayer06
Antony Woods
En este ejemplo de lotería, usted dice que "Solo los eventos pueden causar un cambio de estado en el sistema", pero si el evento es "emitido por un boleto" (y presumiblemente el evento incluye algunos detalles como marca de tiempo, comprador_id, ticket_id), ¿cómo registra el número de referencia del ticket si no hay otro sistema de registro que produzca los identificadores? ¿Existe un sistema CRUD tradicional que necesita producir un boleto antes de que la fuente del evento pueda registrar el hecho en tiempo pasado?
Homan
La acción de emitir el boleto es el evento en este caso. Los datos asociados con el evento son lo que se describe como el evento en su pregunta, lo cual es útil pero técnicamente incorrecto. Además, los eventos generalmente representan una gran cantidad de detalles, donde se pueden componer y descomponer de manera relativamente ilimitada en cada dirección.
theMayer
Uh ... me voló la cabeza con la cosa holarchy.
Homan
Creo que lo que estaba pensando era esto: en el mundo CRUD, especialmente Rails, es común tener identificadores de incremento automático para las claves principales de las tablas. Creamos registros sin conocer los identificadores, el DB me devuelve la identificación del ticket. Ahora, al pasar al mundo de Event Sourcing, por lo que he leído, el evento se 'realiza' antes de que persista en DB, y requiere una identificación agregada. Entonces, en lugar de recuperar la identificación después de la persistencia de DB, parece que la identificación única ya debe conocerse para que pueda describirse como un todo. Parece que siempre deberíamos estar haciendo uuid y no auto-id.
Homan
3

Los comandos y consultas se mantendrán en S3 con el propósito de auditar y restaurar.

Para auditar, claro. ¿Para restaurar ? Eso es raro y es probable que te cause dolores de cabeza.

Si va a ser fuente de eventos, desea rehidratar el estado de los eventos (cosas que sucedieron en el pasado) no los comandos. Esto le ahorra la mayoría de los problemas asociados con los cambios en la implementación de comandos: solo necesita lidiar con los cambios de estado persistentes.

El control de versiones sigue siendo una preocupación. En particular, debe asegurarse de que sus eventos persistentes sean lo más flexibles posible (representaciones de DTO, en lugar de serializaciones directas de los conceptos en su dominio). Al leer eventos de la tienda, tiene la oportunidad de actualizarlos según sea necesario antes de aplicarlos al estado rehidratante.

VoiceOfUnreason
fuente
Ok, entonces creo que tu consejo es que te preocupes menos por la restauración de los comandos y más por los eventos. Por ejemplo, si recibo un comando en la línea de "agregar 10 frijoles", entonces debería emitir y almacenar un evento que diga "se agregaron 10 frijoles. Nuevo total: 40"?
Antony Woods
Si, eso es correcto. Cada cambio de estado en su entidad de origen de eventos está representado por uno o más eventos; para rehidratarse, repites todos esos eventos en orden.
VoiceOfUnreason
2
No acepté esta respuesta, pero quiero agradecerle su contribución, ya que fue vital para enmendar mi comprensión. Elegí la respuesta de rmayer06 solo porque era más directa, redondeada y más útil para alguien que solo accedía a esta pregunta para obtener una respuesta rápida.
Antony Woods