Approvvigionamento di eventi, un evento, stato di due aggregati modificato


10

Sto cercando di imparare modi di DDD e argomenti correlati. Mi è venuta l'idea di un semplice contesto limitato per implementare la "banca": ci sono conti, il denaro può essere depositato, prelevato e trasferito tra di loro. È anche importante conservare la cronologia dei cambiamenti.

Ho identificato l' entità Account e quell'approvvigionamento di eventi sarebbe utile per tenere traccia dei cambiamenti in esso. Altre entità o oggetti valore sono irrilevanti per il problema, quindi non li menzionerò.

Quando si considerano depositi e prelievi, è relativamente semplice, poiché esiste un solo aggregato modificato.

Quando il trasferimento è diverso, due aggregati devono essere modificati da un evento MoneyTransferred . DDD depreca la modifica di più aggregati in una transazione. D'altra parte, la regola del sourcing degli eventi è applicare eventi alle entità e modificare lo stato in base a esse. Se l'evento potesse essere memorizzato semplicemente nel database, non ci sarebbero problemi. Ma per impedire la modifica simultanea di entità di provenienza di eventi, dobbiamo implementare qualcosa che esegue il controllo delle versioni del flusso di eventi di ciascun aggregato (per mantenere i limiti delle transazioni). Con il controllo delle versioni arriva un altro problema: non posso usare strutture semplici per archiviare eventi e rileggerli per applicarli all'aggregazione.

La mia domanda è: come posso mettere insieme questi tre principi: "un aggregato una transazione", "evento-> cambio in aggregato" e "prevenzione delle modifiche simultanee"?

Risposte:


7

Quando il trasferimento è diverso, due aggregati devono essere modificati da un evento MoneyTransferred.

Il trasferimento di denaro è un atto separato dall'aggiornamento dei registri.

MoneyTransferred
AccountCredited
AccountDebited

L'esercizio che alla fine mi ha lasciato libero si stava rendendo conto che si AccountOverdrawntratta di un evento, descrive lo stato dell'account senza riguardo agli altri partecipanti a questo scambio, quindi deve esserci un comando eseguito su un account che lo produce.

Non puoi ragionevolmente derivare uno stato come AccountOverdrawndal modello letto, perché non puoi assolutamente sapere se hai già visto tutti gli eventi - solo l'aggregato stesso ha una visione completa della storia in un dato momento.

La risposta, ovviamente, è proprio lì nel linguaggio onnipresente: i conti vengono accreditati o addebitati per riflettere gli obblighi della banca nei confronti dei propri clienti.

Va bene, ma significa che dovrei usare gli eventi AccountCredited e AccountDebited anche per depositi e prelievi, quindi non registro solo la causa del cambiamento, ma il cambiamento causato da qualche altra azione. Se volessi invertire l'azione non potrei, perché non tutti gli eventi sono registrati.

Non sono del tutto sicuro che segue, perché hai (per casi come questo) un identificatore di correlazione naturale, che è l'id della transazione stessa.

Seconda cosa: significa che devo usare qualcosa come la saga.

Ortografia leggermente diversa: hai bisogno di qualcosa come un essere umano che invia i comandi giusti .

Ci sono almeno due modi per farlo. Uno sarebbe di avere un abbonato che ascolta MoneyTransferrede invia i due comandi ai registri.

Un'altra alternativa sarebbe quella di tenere traccia dell'elaborazione della transazione come aggregato separato: pensala come una lista di controllo di tutte le cose che devono essere fatte da quando si è verificata una transazione. Quindi un MoneyTransferredgestore di eventi invia ProcessTransaction, che pianifica il lavoro da eseguire e verifica quale lavoro è stato completato.


Va bene, ma significa che dovrei usare gli eventi AccountCredited e AccountDebited anche per depositi e prelievi, quindi non registro solo la causa del cambiamento, ma il cambiamento causato da qualche altra azione. Se volessi invertire l'azione non potrei, perché non tutti gli eventi sono registrati. Come posso fare questo (causalità degli eventi)? Seconda cosa: significa che devo usare qualcosa come la saga. Come dovrebbe essere modellato un trasferimento allora? Nel momento in cui ho un metodo di trasferimento sul conto. Quando viene chiamato pubblica l'evento MoneyTransferred . Non so cosa dovrebbe iniziare qualcosa come la saga.
cocsackie,

Non è -> AccountCredited e AccoundDebited quindi MoneyTransferred ? La prima soluzione aggiorna entrambi gli aggregati in un'unica transazione (nessuna garanzia di coerenza di alcun tipo)? Non esiste inoltre alcun aggregato in grado di pubblicare MoneyTransferred -> nessuna correlazione. La seconda soluzione sembra essere migliore: ProcessTransaction può pubblicare MoneyTransferred ed evitare modifiche multiple aggregate in una transazione. Posso pubblicare eventi dal Conto dopo aver eseguito la transazione. Scusa per essere schizzinoso. È difficile da capire per i principianti: non è possibile utilizzare solo un modello senza l'altro.
cocsackie

1

Un dettaglio importante nella comprensione dei conti basati sulle transazioni: l' balanceattributo di accountè in realtà un'istanza di denormalizzazione. È lì per comodità. In realtà, il saldo di un conto è la somma delle sue transazioni e in realtà non è necessario che il conto stesso abbia un saldo.

Tenendo presente ciò, l'atto di trasferire un denaro non dovrebbe essere quello di aggiornare accountma di inserirlo transaction.

Detto questo, c'è un'altra regola importante: l'atto di aggiungere un transactiondovrebbe essere atomico con un aggiornamento al (campo di equilibrio denormalizzato di) account.

Ora, se capisco il concetto di aggregati DDD, sembra rilevante quanto segue :

L'aggregato è un limite logico per le cose che possono cambiare in una transazione commerciale di un determinato contesto. Un aggregato può essere rappresentato da una singola classe o da una moltitudine di classi. Se più di una classe costituisce un aggregato, una di esse è la cosiddetta classe o entità radice. Tutti gli accessi all'aggregato dall'esterno devono avvenire attraverso la classe root.

Quindi, in termini di design DDD, suggerirei:

  1. C'è un aggregato per rappresentare il trasferimento

  2. L'aggregato è composto dai seguenti oggetti: il trasferimento (l'oggetto radice); l'oggetto root è collegato a due elenchi di transazioni (uno per ciascun account); e ogni elenco di transazioni è collegato a un account.

  3. Tutti gli accessi al trasferimento devono essere meditati dall'oggetto root (il transfer).

Se stai cercando di implementare il supporto per il trasferimento asincrono, il tuo codice principale dovrebbe semplicemente preoccuparti di creare il trasferimento, in uno stato "in sospeso". Potresti avere un altro thread o un lavoro che sposta effettivamente i soldi (inserendoli nella cronologia delle transazioni e quindi aggiornando i saldi) e imposta il trasferimento su "registrato".

Se stai cercando di implementare una transazione di trasferimento in tempo reale, bloccando, la logica aziendale dovrebbe creare un transfere quell'oggetto coordinerebbe le altre attività in tempo reale.

In termini di prevenzione dei problemi di concorrenza, il primo ordine di attività dovrebbe essere l'inserimento della transazione di addebito nell'elenco delle transazioni per l'account di origine (aggiornamento del saldo, ovviamente). Questo dovrebbe essere eseguito atomicamente a livello di database (tramite una procedura memorizzata). Dopo che si è verificato l'addebito, il resto del trasferimento dovrebbe riuscire a prescindere dai problemi di concorrenza, in quanto non dovrebbero esserci regole commerciali che impediscono un credito sul conto di destinazione.

(Nel mondo reale, i conti bancari hanno il concetto di un memo post che supporta il concetto di un lazy commit in due fasi. La creazione del post dei memo è leggera e facile, e può anche essere ripristinata senza problemi. Conversione del il post di promemoria su un post rigido è quando il denaro si sposta effettivamente (non è possibile eseguire il rollback) e rappresenta la seconda fase del commit in due fasi, che si verifica solo dopo aver verificato tutte le regole di convalida).


0

Sono anche attualmente in fase di apprendimento. Da un punto di vista dell'implementazione, è così che sento che eseguirai questa azione.

Dispatch TransferMoneyCommand che genera i seguenti eventi [MoneyTransferEvent, AccountDebitedEvent]

Prima di generare questi eventi, è necessario eseguire la convalida dei comandi superficiali e la convalida della logica di dominio, ovvero se l'account ha un saldo sufficiente?

Persistere gli eventi (con controllo delle versioni) per garantire che non vi siano problemi di coerenza. Si noti che potrebbe esserci un altro comando simultaneo (come prelevare tutto il denaro) che è riuscito a salvare e salvare eventi prima di questo, quindi lo stato corrente dell'aggregato potrebbe non essere aggiornato e quindi gli eventi vengono generati sul vecchio stato e non sono corretti. Se il salvataggio degli eventi fallisce, dovrai ripetere il comando dall'inizio.

Dopo che gli eventi sono stati salvati correttamente nel database, è possibile pubblicare i due eventi che sono stati generati.

AccountDebitedEvent rimuoverà i soldi dal conto del pagatore (aggiorna lo stato aggregato e tutti i relativi modelli di vista / proiezione)

MoneyTransferEvent avvia Saga / Process Manager.

Il lavoro del responsabile della saga / processo sarà quello di provare a accreditare il conto del beneficiario, in caso contrario, dovrà riaccreditare il saldo al pagatore.

Il manager di Saga / Process pubblicherà un CreditAccountCommand che verrà applicato all'account del beneficiario e in caso di esito positivo rispetto a AccountCreditedEvent verrà generato.

Dal punto di vista dell'approvvigionamento di eventi, se si desidera invertire questa azione, tutti gli eventi in questa transazione avranno l'id di correlazione / causalità come TransferMoneyCommand originale che è possibile utilizzare per generare eventi per le operazioni di annullamento / annullamento.

Sentiti libero di suggerire eventuali problemi o potenziali miglioramenti su quanto sopra.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.