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

10

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

Identifiqué la entidad de la cuenta y ese abastecimiento de eventos sería bueno para realizar 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 la transferencia es diferente, dos eventos deben ser modificados por un evento MoneyTransferred . DDD desprecia la modificación de múltiples agregados en una transacción. Por otro lado, la regla del abastecimiento de eventos es aplicar eventos a entidades y modificar el estado en función de ellos. Si el evento pudiera almacenarse simplemente en la base de datos, no habría problema. Pero para evitar la modificación concurrente de las entidades de origen de eventos, debemos implementar algo que versione el flujo de eventos de cada agregado (para mantener sus límites de transacción). Con el control de versiones 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 y una", "evento-> cambio en agregación" y "prevención de modificación concurrente"?

cocsackie
fuente

Respuestas:

7

Cuando la transferencia es diferente, dos eventos deben ser modificados por un evento MoneyTransferred.

Transferir dinero es un acto separado de actualizar los libros de contabilidad.

MoneyTransferred
AccountCredited
AccountDebited

El ejercicio que finalmente me liberó fue darme cuenta de que AccountOverdrawnes un evento, describe el estado de la cuenta sin tener en cuenta a los otros participantes en este intercambio, por lo que debe haber un comando ejecutado contra una cuenta que lo produce.

No puede derivar razonablemente el estado AccountOverdrawndel modelo de lectura, porque posiblemente no puede saber si ya ha visto todos los eventos; solo el agregado en sí tiene una vista completa de la historia en un momento dado.

La respuesta, por supuesto, está ahí en el lenguaje omnipresente: las cuentas se acreditan o debitan para reflejar las obligaciones del banco con sus clientes.

Está bien, pero significa que también debería usar los eventos AccountCredited y AccountDebited para depósitos y retiros, por lo que solo no registro la causa del cambio, sino 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 sí tiene (para casos como este) un identificador de correlación natural, que es la identificación de la transacción en sí.

En segundo lugar, significa que necesito usar algo como saga.

Ortografía ligeramente diferente: necesitas algo como un ser humano que envía los comandos correctos .

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

Otra alternativa sería rastrear el procesamiento de la transacción como un agregado separado; considérelo como una lista de verificación de todas las cosas que deben hacerse desde que ocurrió una transacción. Entonces, un MoneyTransferredcontrolador de eventos distribuye ProcessTransaction, que programa el trabajo a realizar y verifica qué trabajo se ha completado.

VoiceOfUnreason
fuente
Está bien, pero significa que también debería usar los eventos AccountCredited y AccountDebited para depósitos y retiros, por lo que solo no registro la causa del cambio, sino 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. ¿Cómo puedo hacer esto (causalidad de eventos)? En segundo lugar, significa que necesito usar algo como saga. ¿Cómo debería ser una transferencia modelada entonces? En el momento tengo el método de transferencia a cuenta. Cuando se llama, publica el evento MoneyTransferred . No sé qué debería comenzar algo como la saga.
cocsackie
¿No es -> AccountCredited y AccoundDebited y luego MoneyTransferred ? ¿La primera solución actualiza ambos agregados en una transacción (sin garantía de coherencia de ningún tipo)? Tampoco hay un agregado que pueda publicar MoneyTransferred -> sin correlación. La segunda solución parece ser mejor: ProcessTransaction puede publicar MoneyTransferred y para evitar múltiples modificaciones agregadas en una transacción, puedo publicar eventos desde la cuenta después de confirmar la transacción. Perdón por ser quisquilloso. Es difícil de entender para principiantes: no puede usar solo un patrón sin otro.
cocsackie
1

Un detalle importante para comprender las cuentas basadas en transacciones: el balanceatributo de accountes 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 que la cuenta misma tenga un saldo.

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

Dicho esto, hay otra regla importante: el acto de agregar un transactiondebe ser atómico con una actualización del (campo de equilibrio desnormalizado de) account.

Ahora, si entiendo el concepto DDD de agregados, lo 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 el acceso al agregado desde el exterior debe realizarse a través de la clase raíz.

Entonces, en términos de diseño 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. Todo el acceso a la transferencia debe ser meditado por el objeto raíz (el transfer).

Si está intentando implementar el soporte de transferencia asíncrono, entonces su código principal debería 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 está buscando implementar una transacción de transferencia de bloqueo en tiempo real, entonces la lógica de negocios debería crear un transferobjeto que coordinaría las otras actividades en tiempo real.

En términos de prevención de problemas de concurrencia, el primer orden comercial debe ser insertar la transacción de débito en la lista de transacciones de la cuenta de origen (actualizando el saldo, por supuesto). Esto debería realizarse atómicamente a nivel de la base de datos (a través de un procedimiento almacenado). Después de que se haya producido 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 en la cuenta objetivo.

(En el mundo real, las cuentas bancarias tienen el concepto de una publicación de notas que respalda el concepto de una confirmación diferida de dos fases. La creación de la publicación de notas es liviana y fácil, y también puede revertirse sin problemas. la publicación de la nota en una publicación dura 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).

John Wu
fuente
0

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

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

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

Persista los eventos (con 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 se generan en el estado anterior y son incorrectos. Si falla el guardado de eventos, deberá volver a intentar el comando desde el principio.

Una vez que los eventos se guardan 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 administrador de la saga / proceso será tratar de 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 del abastecimiento de eventos, si desea revertir esta acción, todos los eventos en esta transacción tendrán la identificación de correlación / causalidad como el TransferMoneyCommand original que puede usar para generar eventos para operaciones de deshacer / revertir.

Siéntase libre de sugerir cualquier problema o posible mejora en lo anterior.

Shayan C
fuente