Sorgente di eventi, un evento, lo stato di due aggregati è cambiato

9

Sto cercando di imparare modi di DDD e argomenti correlati. Mi è venuta l'idea di un semplice contesto limitato per implementare "banca": ci sono account, denaro può essere depositato, ritirato e trasferito tra di loro. È anche importante mantenere la cronologia delle modifiche.

Ho identificato l'entità Account e il fatto che l'individuazione degli eventi sarebbe utile per tenere traccia delle modifiche apportate. Altre entità o oggetti valore sono irrilevanti per il problema, quindi non ne parlerò.

Quando si considerano depositi e prelievi - è relativamente semplice, perché è stato modificato solo un aggregato.

Durante 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 dell'outsour degli eventi consiste nell'applicare gli eventi alle entità e modificare lo stato in base ad essi. Se l'evento potesse essere memorizzato semplicemente nel database, non ci sarebbero problemi. Tuttavia, per impedire modifiche simultanee di entità originate da eventi, è necessario implementare versioni di 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 gli eventi e leggerli di nuovo per applicarli all'aggregazione.

La mia domanda è: come posso riunire questi tre principi: "una sola transazione aggregata", "evento- > modifica in aggregato" e "prevenzione della modifica simultanea"?

    
posta cocsackie 11.02.2017 - 05:09
fonte

3 risposte

5

When transferring it's different - two aggregates must be modified by one MoneyTransferred event.

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

MoneyTransferred
AccountCredited
AccountDebited

L'esercizio che alla fine mi ha sbloccato è stato rendermi conto che AccountOverdrawn è un evento, descrive lo stato dell'account senza riguardo agli altri partecipanti a questo scambio, quindi è necessario eseguire un comando su un account che lo produce.

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

La risposta, naturalmente, è proprio lì nella lingua onnipresente: i conti sono accreditati o addebitati per riflettere gli obblighi della banca nei confronti dei propri clienti.

Allright, but it means I should use AccountCredited and AccountDebited events for deposits and withdrawals as well, so I only register not the cause of change, but the change caused by some other action. If I would like to reverse the action I couldn't, because not all of the events are registered.

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

Second thing - it means i need to use something like 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 quello di avere un sottoscrittore in ascolto per MoneyTransferred e di inviare i due comandi ai libri mastri.

Un'altra alternativa sarebbe quella di tracciare l'elaborazione della transazione come un aggregato separato: pensateci come una lista di controllo di tutte le cose che devono essere eseguite da quando si è verificata una transazione. Quindi un gestore di eventi MoneyTransferred invia ProcessTransaction, che pianifica il lavoro da eseguire e controlla il lavoro che è stato completato.

    
risposta data 11.02.2017 - 07:41
fonte
1

Un dettaglio importante nella comprensione degli account basati sulle transazioni: l'attributo balance di account è in realtà un'istanza di denormalizzazione. È lì per comodità. In realtà il saldo di un account è la somma delle sue transazioni, e non hai bisogno dell'account stesso per avere un saldo.

Tenendo presente questo, l'atto di trasferire un denaro non dovrebbe essere quello di aggiornare account ma di inserire in transaction .

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

Ora, se comprendo il concetto DDD di aggregati, il seguito sembra pertinente :

The aggregate is a logical boundary for things that can change in a business transaction of a given context. An aggregate can be represented by a single class or by a multitude of classes. If more than one class constitutes to an aggregate then one of them is the so called root class or entity. All access to the aggregate from outside has to happen through the root class.

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 radice è 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 dovrebbero essere meditati dall'oggetto root (il transfer ).

Se stai cercando di implementare il supporto per il trasferimento asincrono, il tuo codice principale dovrebbe semplicemente preoccuparsi di creare il trasferimento, in uno stato "in sospeso". Potresti avere un'altra discussione o un lavoro che sposta effettivamente il denaro (inserendolo nella cronologia delle transazioni e quindi aggiornando i saldi) e imposta il trasferimento su "pubblicato".

Se stai cercando di implementare una transazione di trasferimento in tempo reale e bloccante, la logica di business dovrebbe creare un transfer e quell'oggetto coordinerebbe le altre attività in tempo reale.

In termini di prevenzione dei problemi di concorrenza, il primo ordine del business dovrebbe essere quello di inserire la transazione di debito nella lista delle transazioni per l'account sorgente (aggiornando il saldo, ovviamente). Questo dovrebbe essere eseguito atomicamente a livello di database (tramite una stored procedure). Dopo che l'addebito si è verificato, il resto del trasferimento dovrebbe essere in grado di avere successo a prescindere dai problemi di concorrenza, in quanto non dovrebbero esserci regole aziendali che impediscano il credito all'account di destinazione.

(Nel mondo reale, i conti bancari hanno il concetto di un memo post che supporta un concetto di pigro due -phase commit La creazione del memo post è leggera e facile, e può anche essere ripristinata senza problemi Conversione del post memo su un post difficile è quando il denaro si muove effettivamente-- questo non può essere ripristinato-- e rappresenta la seconda fase del commit a due fasi, che si verifica solo dopo aver verificato tutte le regole di convalida).

    
risposta data 14.02.2017 - 23:56
fonte
0

Sono anche attualmente in fase di apprendimento. Dal punto di vista dell'implementazione, questo è il modo in cui ritengo che eseguirai questa azione.

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

Nota prima di sollevare questi eventi, sarà necessario eseguire la convalida dei comandi superficiali e la convalida della logica di dominio, ovvero l'account ha abbastanza equilibrio?

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

Una volta che gli eventi sono stati salvati correttamente nel database, puoi pubblicare i due eventi che sono stati generati.

AccountDebitedEvent rimuoverà il denaro dall'account del pagatore (aggiorna lo stato aggregato e qualsiasi modello di visualizzazione / proiezione correlato)

MoneyTransferEvent avvia Saga / Process Manager.

Il lavoro della saga / process manager sarà di provare ad accreditare sul conto del beneficiario, se fallisce, sarà necessario accreditare il saldo al pagatore.

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

Da un punto di vista del sourcing di un evento, se si desidera invertire questa azione, tutti gli eventi in questa transazione avranno l'id di correlazione / causalità del TransferMoneyCommand originale che è possibile utilizzare per generare eventi per operazioni di annullamento / inversione.

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

    
risposta data 30.11.2018 - 13:55
fonte

Leggi altre domande sui tag