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.
Esbocé mi comprensión aproximada de cómo debería verse una aplicación ES / CQRS contextualizada a un caso de uso bancario simplificado (retirar dinero).
Para resumir, si la persona A retira algo de dinero:
- se emite un comando
- el comando se entrega 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 retira 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, entonces podemos derivar cualquier proyección 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 una orden de retiro de 100e
- la validación pasa y se emite el evento MoneyWithdrewEvent of 100e
- Mientras tanto, la persona A emite otra orden de retirada de 100e
- el primer MoneyWithdrewEvent no se agregó todavía, por lo tanto, la validación pasa, porque la verificación de validación con el agregado (que aún no se ha actualizado)
- MoneyWithdrewEvent de 100e se emite otra vez
==> Estamos en un estado inconsistente de un saldo de -100e y el registro contiene 2 MoneyWithdrewEvent
Según tengo entendido, hay varias estrategias para hacer frente a este problema:
- a) coloque la identificación de versión agregada junto con el evento en el almacén de eventos, por lo que si hay una versión que no coincide con la modificación, no sucede nada
- b) utilice algunas estrategias de bloqueo, lo que implica que la capa de verificación tiene que crear una 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 él? Además, regresamos al cliente bien, mientras que era totalmente incorrecto permitir el retiro, ¿es mejor en este caso usar bloqueos?
- b) Bloqueos == puntos muertos, ¿tiene alguna idea sobre las mejores prácticas?
En general, ¿entiendo bien 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 perderme en detalles
fuente
Respuestas:
Este es el ejemplo perfecto de una aplicación de origen de eventos. Empecemos.
Cada vez que se procesa o reintenta un comando (lo comprenderá, sea paciente) se realizan los siguientes pasos:
Application layer
.Aggregate
y carga desde el repositorio (en este caso, la carga se realiza haciendonew
unaAggregate
instancia, recuperando todos los eventos emitidos previamente de este agregado y volviéndolos a aplicar al Agregado mismo; la versión del Agregado se almacena para uso posterior; después de que se aplican los eventos, el Agregado está en su estado final, es decir, el saldo de la cuenta corriente se calcula como un número)Aggregate
, comoAccount::withdrawMoney(100)
y recopila los eventos producidos, es decirMoneyWithdrewEvent(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.Aggregate
en el repositorio (en este caso, el repositorio es elEvent Store
); lo hace agregando los nuevos eventos alEvent stream
if y solo si elversion
delAggregate
todavía es el que estaba cuandoAggregate
se cargó. Si la versión no es la misma, el comando se vuelve a intentar; vaya al paso 1 . Siversion
es igual, los eventos se agregan alEvent stream
y se le proporciona al cliente elSuccess
estado.Esta comprobación de versión se denomina bloqueo optimista y es un mecanismo de bloqueo general. Otro mecanismo es el bloqueo pesimista. cuando se otras escrituras (como en no iniciado) hasta que se completa la actual.
El termino
Event stream
es una abstracción en torno a todos los eventos emitidos por el mismo Agregado.Debe comprender que
Event store
es solo otro tipo de persistencia donde se almacenan todos los cambios en un agregado, no solo el estado final.La tienda de eventos es siempre la fuente de la verdad.
Al usar el bloqueo optimista no tienes bloqueos, solo ordena volver a intentarlo.
De todos modos, ¡Cerraduras! = Puntos muertos
fuente
Aggregate
lugar en el que no aplica todos los eventos, pero mantiene una instantánea deAggregate
hasta un punto en el pasado y aplica solo los eventos que ocurrieron después de ese punto.Aggregate
, ¿cuándo debe actualizarse la instantánea? ¿La tienda de instantáneas es la misma que la tienda de eventos o es una vista materializada derivada del bus de eventos?Cerca. El problema es que la lógica para actualizar su "agregado" está en un lugar extraño.
La implementación más habitual es que el modelo de datos que su controlador de comandos guarda 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 sincrónicas en el almacén de eventos y actualiza su copia local del modelo si la conexión al almacén de eventos indica que la escritura se realizó correctamente.
Si el controlador de comandos necesita resincronizarse 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 estarían conectadas a la tienda de eventos, no a una tienda agregada.
Las variaciones de este son el caso habitual: en lugar de agregarse a la secuencia en la secuencia de eventos, generalmente PONEMOS 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, reintentar, fusionar ...). 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.
fuente