Manejo de concurrencia ES / CQRS

14

Recientemente comencé a sumergirme en CQRS / ES porque podría necesitar aplicarlo en el trabajo. Parece muy prometedor en nuestro caso, ya que resolvería muchos problemas.

Esbozo mi comprensión aproximada de cómo una aplicación de ES / CQRS debe parecerse contextualizada a un caso de uso bancario simplificado (retirar dinero).

Para resumir, si la persona A retira algo de dinero:

  • se emite un comando
  • se entrega el comando para validación / verificación
  • un evento se envía a un almacén de eventos si la validación se realiza correctamente
  • un agregador pone en cola el evento para aplicar modificaciones en el agregado

Por lo que entendí, el registro de eventos es la fuente de la verdad, ya que es el registro de HECHOS, podemos derivar cualquier proyección a partir de él.

Ahora, lo que no entiendo, en este gran esquema de cosas, es lo que sucede en este caso:

  • regla: un saldo no puede ser negativo
  • la persona A tiene un saldo de 100e
  • la persona A emite un comando WithdrawCommand de 100e
  • pases de validación y se emite el evento MoneyWithdrewEvent de 100e
  • mientras tanto, la persona A emite otro WithdrawCommand de 100e
  • el primer MoneyWithdrewEvent no se agregó, pero la validación pasa, porque la verificación de la validación contra el agregado (que aún no se ha actualizado)
  • MoneyWithdrewEvent de 100e se emite en otro momento

== > Estamos en un estado inconsistente de un saldo en -100e y el registro contiene 2 MoneyWithdrewEvent

Según tengo entendido, hay varias estrategias para hacer frente a este problema:

  • a) coloque el id de la versión agregada junto con el evento en el almacén de eventos, de modo que si hay una discrepancia en la versión tras la modificación, no suceda nada
  • b) utilice algunas estrategias de bloqueo, lo que implica que la capa de verificación tiene que crear de alguna manera

Preguntas relacionadas con las estrategias:

  • a) En este caso, el registro de eventos ya no es la fuente de la verdad, ¿cómo lidiar con eso? Además, volvimos al cliente de acuerdo, mientras que era totalmente incorrecto permitir el retiro, ¿es mejor en este caso usar cerraduras?
  • b) Locks == deadlocks, ¿tiene alguna idea sobre las mejores prácticas?

En general, ¿es correcto mi entendimiento sobre cómo manejar la concurrencia?

Nota: entiendo que la misma persona que retira dos veces el dinero en tan poco tiempo es imposible, pero tomé un ejemplo simple, para no perderse en los detalles

    
pregunta Louis F. 24.05.2017 - 20:57

2 respuestas

14
  

Esbozo mi comprensión aproximada de cómo una aplicación de ES / CQRS debe parecerse contextualizada a un caso de uso bancario simplificado (retirar dinero).

Este es el ejemplo perfecto de una aplicación de origen de eventos. Vamos a empezar.

Cada vez que se procesa o se reintenta un comando (comprenderá, tenga paciencia) se realizan los siguientes pasos:

  1. el comando llega a un controlador de comandos, es decir, un servicio en el Application layer .
  2. el controlador de comandos identifica el Aggregate y lo carga desde el repositorio (en este caso, la carga se realiza mediante new -ing una instancia Aggregate , recuperando todos los eventos emitidos previamente de este agregado y volviendo a aplicarlos) al Aggregate mismo; la versión de Aggregate se almacena para su uso posterior; después de que se aplican los eventos, el Aggregate se encuentra en su estado final, es decir, el saldo de la cuenta actual se calcula como un número)
  3. el controlador de comandos llama al método apropiado en el Aggregate , como Account::withdrawMoney(100) y recopila los eventos producidos, es decir, MoneyWithdrewEvent(AccountId, 100) ; si no hay suficiente dinero en la cuenta (saldo < 100), se genera una excepción y se cancela todo; de lo contrario, se realiza el siguiente paso.
  4. el controlador de comandos intenta mantener el Aggregate en el repositorio (en este caso, el repositorio es el Event Store ); lo hace agregando los nuevos eventos al Event stream si y solo si el version del Aggregate sigue siendo el que estaba cuando se cargó el Aggregate . Si la versión no es la misma, entonces se reintenta el comando; vaya al paso 1 . Si el version es el mismo, entonces los eventos se agregan al Event stream y el cliente recibe el estado Success .

Esta comprobación de versión se denomina bloqueo optimista y es un mecanismo de bloqueo general. Otro mecanismo es bloqueo pesimista cuando se bloquean otras escrituras (como no se ha iniciado) hasta que se completa la actual.

El término Event stream es una abstracción alrededor de todos los eventos que fueron emitidos por el mismo Agregado.

Debes entender que el Event store es solo otro tipo de persistencia donde se almacenan todos los cambios en un Agregado, no solo el estado final.

  

a) En este caso, el registro de eventos ya no es la fuente de la verdad, ¿cómo lidiar con eso? Además, volvimos al cliente de acuerdo, mientras que era totalmente incorrecto permitir el retiro, ¿es mejor en este caso usar bloqueos?

El almacén de eventos es siempre la fuente de la verdad.

  

b) Locks == deadlocks, ¿tiene alguna idea sobre las mejores prácticas?

Al utilizar el bloqueo optimista, no tiene bloqueos, simplemente reintente el comando.

De todos modos, ¡Cerraduras! = puntos muertos

    
respondido por el Constantin Galbenu 25.05.2017 - 08:44
1
  

Esbozo mi comprensión aproximada de cómo una aplicación de ES / CQRS debe parecerse contextualizada a un caso de uso bancario simplificado (retirar dinero).

Cerrar. El problema es que la lógica para actualizar tu "agregado" está en un lugar extraño.

La implementación más habitual es que el modelo de datos que su controlador de comandos mantiene en la memoria y la secuencia de eventos en el almacén de eventos se mantienen sincronizados.

Un ejemplo fácil de describir es el caso en el que el controlador de comandos realiza escrituras síncronas en el almacén de eventos y actualiza su copia local del modelo si la conexión con el almacén de eventos indica que la escritura se realizó correctamente.

Si el controlador de comandos necesita volver a sincronizarse con el almacén de eventos (porque su modelo interno no coincide con el de la tienda), lo hace cargando el historial de la tienda y reconstruyendo su propio estado interno.

En otras palabras, las flechas 2 y 3 (si están presentes) normalmente se conectarán al almacén de eventos, no a un almacén agregado.

  

coloque el id de la versión agregada junto con el evento en el almacén de eventos, de modo que si hay una discrepancia de versión en la modificación, no suceda nada

Las variaciones de esto son el caso habitual; en lugar de adjuntar a la secuencia en la secuencia de eventos, normalmente PUT a una ubicación específica en la secuencia; Si esa operación es incompatible con el estado de la tienda, la escritura falla y el servicio puede elegir el modo de falla apropiado (falla al cliente, reintente, fusione ...). El uso de escrituras idempotentes resuelve una serie de problemas en la mensajería distribuida, pero, por supuesto, requiere tener una tienda que admita una escritura idempotente.

    
respondido por el VoiceOfUnreason 24.05.2017 - 21:50

Lea otras preguntas en las etiquetas