Fuente de eventos, un evento, el estado de dos agregados cambió

8

Estoy tratando de aprender formas de DDD y temas relacionados. Se me ocurrió una idea de contexto limitado para implementar "banco": hay cuentas, el dinero se puede depositar, retirar y transferir entre ellos. También es importante mantener el historial de cambios.

Identifiqué la entidad Cuenta y que el abastecimiento de eventos sería bueno para hacer un seguimiento de los cambios en ella. Otras entidades u objetos de valor son irrelevantes para el problema, por lo que no los mencionaré.

Al considerar los depósitos y retiros, es relativamente simple, porque solo hay un agregado modificado.

Cuando se transfiere es diferente: dos eventos deben ser modificados por un evento MoneyTransferred . DDD desaprueba la modificación de múltiples agregados en una transacción. Por otro lado, la regla de la fuente de eventos es aplicar eventos a las entidades y modificar el estado en función de ellas. Si el evento se pudiera almacenar simplemente en la base de datos, no habría ningún problema. Pero para evitar la modificación concurrente de las entidades de origen de eventos, debemos implementar algo en la versión del flujo de eventos de cada agregado (para mantener sus límites de transacción). Con el versionado viene otro problema: no puedo usar estructuras simples para almacenar eventos y volver a leerlos para aplicarlos al agregado.

Mi pregunta es: ¿cómo puedo reunir esos tres principios: "una transacción agregada una", "cambio en el evento agregado > y" prevención de modificación concurrente "?

    
pregunta cocsackie 11.02.2017 - 05:09

3 respuestas

5
  

Cuando se transfiere es diferente: dos eventos deben ser modificados por un evento MoneyTransferred.

La transferencia de dinero es un acto separado de la actualización de los libros de contabilidad.

MoneyTransferred
AccountCredited
AccountDebited

El ejercicio que finalmente rompió esto se dio cuenta de que AccountOverdrawn es un evento, describe el estado de la cuenta sin tener en cuenta a los demás participantes en este intercambio, por lo que debe ejecutarse un comando contra una cuenta que lo produce.

No se puede derivar razonablemente un estado como AccountOverdrawn del modelo de lectura, ya que posiblemente no se puede saber si ya se han visto todos los eventos; solo el agregado en sí tiene una vista completa del historial en cualquier momento dado.

La respuesta, por supuesto, está allí mismo en el lenguaje ubicuo: las cuentas se acreditan o se cargan para reflejar las obligaciones del banco para con sus clientes.

  

Está bien, pero significa que también debería usar los eventos AccountCredited y AccountDebited para depósitos y retiros, así que solo registro la causa del cambio, pero el cambio causado por alguna otra acción. Si quisiera revertir la acción no podría, porque no todos los eventos están registrados.

No estoy completamente seguro de lo que sigue, porque tienes (para casos como este) un identificador de correlación natural, que es el identificador de transacción en sí mismo.

  

Lo segundo: significa que necesito usar algo como saga.

Ortografía ligeramente diferente: necesita algo como un ser humano enviando los comandos correctos .

Hay al menos dos formas de hacerlo. Uno sería tener un suscriptor escuchando MoneyTransferred y enviando los dos comandos a los libros de contabilidad.

Otra alternativa sería rastrear el procesamiento de la transacción como un agregado separado. Piense en ello como una lista de verificación de todas las cosas que deben realizarse desde que se realizó la transacción. Por lo tanto, un controlador de eventos MoneyTransferred distribuye ProcessTransaction, que programa el trabajo a realizar y verifica qué trabajo se ha completado.

    
respondido por el VoiceOfUnreason 11.02.2017 - 07:41
1

Un detalle importante para comprender las cuentas basadas en transacciones: el atributo balance de account es en realidad una instancia de desnormalización. Está ahí por conveniencia. En realidad, el saldo de una cuenta es la suma de sus transacciones, y realmente no necesita la cuenta en sí misma para tener un saldo.

Teniendo esto en cuenta, el acto de transferir dinero no debe ser actualizar account sino insertar en transaction .

Dicho esto, hay otra regla importante: el hecho de agregar transaction debe ser atómico con una actualización del campo (saldo normalizado de) account .

Ahora, si entiendo el concepto DDD de agregados, el siguiente parece relevante :

  

El agregado es un límite lógico para cosas que pueden cambiar en una transacción comercial de un contexto dado. Un agregado puede ser representado por una sola clase o por una multitud de clases. Si más de una clase constituye un agregado, una de ellas es la llamada clase o entidad raíz. Todo acceso al agregado desde el exterior tiene que pasar a través de la clase raíz.

Así que en términos de diseño de DDD sugeriría:

  1. Hay un agregado para representar la transferencia

  2. El agregado se compone de los siguientes objetos: la transferencia (el objeto raíz); el objeto raíz está vinculado a dos listas de transacciones (una para cada cuenta); y cada lista de transacciones está vinculada a una cuenta.

  3. Todos los accesos a la transferencia deben ser meditados por el objeto raíz (el transfer ).

Si está intentando implementar un soporte de transferencia asíncrono, entonces su código principal solo debe preocuparse por crear la transferencia, en un estado "pendiente". Es posible que tenga otro hilo o un trabajo que realmente mueva el dinero (insertándolo en el historial de transacciones y, por lo tanto, actualizando los saldos) y establezca la transferencia en "contabilizada".

Si desea implementar una transacción de transferencia de bloqueo en tiempo real, la lógica de negocios debe crear un transfer y ese objeto coordinará las otras actividades en tiempo real.

En lo que respecta a la prevención de problemas de concurrencia, el primer asunto debe consistir en insertar la transacción de débito en la lista de transacciones de la cuenta de origen (actualizar el saldo, por supuesto). Esto debería realizarse de forma atómica en el nivel de la base de datos (a través de un procedimiento almacenado). Después de que se haya realizado el débito, el resto de la transferencia debería poder tener éxito independientemente de los problemas de concurrencia, ya que no debería haber ninguna regla comercial que impida un crédito a la cuenta de destino.

(En el mundo real, las cuentas bancarias tienen el concepto de una publicación de notas que respalda el concepto de dos perezosos -fase de confirmación. La creación de la publicación de notas es ligera y fácil, y también se puede revertir sin problemas. La conversión de la publicación de notas a una publicación difícil es cuando el dinero realmente se mueve, esto no se puede revertir. y representa la segunda fase de la confirmación de dos fases, que ocurre solo después de que se hayan verificado todas las reglas de validación).

    
respondido por el John Wu 14.02.2017 - 23:56
0

También estoy actualmente en la etapa de aprendizaje. Desde el punto de vista de la implementación, así es como creo que realizará esta acción.

Dispatch TransferMoneyCommand que genera los siguientes eventos [MoneyTransferEvent, AccountDebitedEvent]

Tenga en cuenta que antes de que se produzcan estos eventos, será necesario realizar la validación superficial de comandos y la validación de la lógica del dominio, es decir, ¿la cuenta tiene saldo suficiente?

Persista los eventos (con el control de versiones) para asegurarse de que no haya problemas de coherencia. Tenga en cuenta que podría haber otro comando concurrente (como retirar todo el dinero) que logró tener éxito y guardar eventos antes de este, por lo que el estado actual del agregado puede estar desactualizado y, por lo tanto, los eventos aparecen en el estado anterior y son incorrectos. Si el guardado de eventos falla, deberá volver a intentar el comando desde el principio.

Una vez que los eventos se hayan guardado correctamente en la base de datos, puede publicar los dos eventos que se generaron.

AccountDebitedEvent eliminará el dinero de la cuenta del pagador (actualiza el estado agregado y cualquier modelo de vista / proyección relacionado)

MoneyTransferEvent inicia Saga / Process Manager.

El trabajo del gerente de la saga / proceso será intentar acreditar la cuenta del beneficiario; si falla, deberá acreditar el saldo al pagador.

Saga / Process manager publicará un CreditAccountCommand que se aplica a la cuenta del beneficiario y, si tiene éxito, se generará AccountCreditedEvent.

Desde el punto de vista de una fuente de eventos, si desea revertir esta acción, todos los eventos en esta transacción tendrán el identificador de correlación / causación como el TransferMoneyCommand original que puede usar para generar eventos para operaciones de deshacer / reversa.

No dude en sugerir cualquier problema o posible mejora en lo anterior.

    
respondido por el Shayan C 30.11.2018 - 13:55

Lea otras preguntas en las etiquetas