DDD: gestione dell'incoerenza temporanea durante la gestione degli eventi di dominio

0

Comprendo che DDD sostiene coerenza finale , consentendo così un certo periodo di tempo in cui il sistema potrebbe essere incoerente. Abbracciando la coerenza finale, possiamo quindi modellare i nostri aggregati in modo tale che solo 1 sia aggiornato per transazione.

Tuttavia, la mia domanda è: cosa succede se la relazione tra più aggregati è tale che la coerenza finale non è accettabile o le azioni di compensazione sarebbero troppo complesse da implementare? Ciò significa che DDD non è la scelta giusta in questo scenario, o significa che semplicemente non possiamo usare la regola 1 aggregato per transazione? O forse questo significa che i multipli aggregati devono essere uniti in un aggregato più grande?

Per dare un esempio più concreto, considera quanto segue: Un sistema di aste, composto da Articoli, Aste, Offerte, Utenti e UserBalance. Gli utenti possono fare un'offerta su un'asta se sono soddisfatte le seguenti condizioni:

  • Hanno superato l'attuale offerta più alta, se esistente

  • Il loro saldo ha fondi sufficienti per piazzare l'offerta

Quando un'offerta è superata, l'utente che ha piazzato l'offerta dovrebbe ricevere i fondi utilizzati nel collocare quell'offerta. Gli utenti possono aumentare la loro UserBalance con altre azioni, come acquisti, premi, ecc.

Ho immaginato i seguenti aggregati: asta composta di offerte, utente e utenteBalance.

Imagine Auction ha un metodo chiamato placeBid che gestisce la logica del posizionamento dell'offerta.

Per preservare la regola 1 aggregato per transazione, l'Asta dovrebbe accettare un oggetto utenteBalance e verificare se il posizionamento dell'offerta è valido. Se sì, procede quindi a creare un'offerta, a impegnare la transazione e pubblicare un evento di dominio. Tuttavia, può accadere che un utente non abbia più fondi sufficienti per pagare un'offerta al momento dell'avvio di BidPlacedHandler. Ciò significa che altri utenti vedranno l'offerta appena posizionata prima che il saldo venga aggiornato. Ciò significa anche che una volta fallito l'aggiornamento di UserBalance, sarà necessario eliminare l'attuale offerta più alta e ripristinare l'offerta precedente come l'offerta più alta, a meno che, naturalmente, non sia stata inserita un'altra offerta nel frattempo.

Mentre la gestione di questi casi compensativi potrebbe essere leggermente illeggibile o difficile da comprendere, la mia più grande preoccupazione è il fatto che gli utenti potrebbero vedere un'offerta non valida come l'offerta più alta per un'asta. Esistono linee guida su come affrontare questo problema, in questo caso particolare o in generale?

    
posta Stefan Rendevski 20.11.2018 - 16:22
fonte

1 risposta

1

Voglio iniziare da questo affrontando innanzitutto lo spazio del problema qui, poiché vedo un po 'di malinteso riguardo sia alla "coerenza finale" che al DDD.

DDD è un processo design che capisce che i problemi del mondo reale a volte raggiungono un livello di complessità che non possono essere separati in modo pulito e modellati in unità completamente indipendenti. Cioè, process stesso può avere regole e quando si verifica una tale situazione, è possibile che la coerenza possa essere impiegata per offrire una soluzione. Non leggo la citazione che offri dal libro blu (nei commenti OP) come a favore di qualsiasi modalità di coerenza, piuttosto sottolineando che la coerenza finale è spesso un attributo desiderabile e talvolta necessario all'interno di un sistema di cui dobbiamo essere consapevole quando si formano le nostre aspettative.

Se un determinato sistema ha bisogno di impiegare un eventuale paradigma coerente dovrebbe essere determinato attraverso l'analisi dei suoi requisiti in termini di ridimensionamento, non attraverso l'analisi del suo design (sebbene il precedente possa certamente informare il quest'ultimo). Questa decisione non dovrebbe essere presa alla leggera (e non deve essere all-or-nothing), perché i trade-off sono ripidi e spesso richiedono molto una maggiore complessità in termini di implementazione dove impiegati.

Per quanto riguarda il tuo sistema, penso che potresti aver soddisfatto un paio di cose insieme.

Il primo è che un Auction sarebbe meglio compreso / implementato come Saga / ProcessManger piuttosto che Aggregate . Cioè, un Auction rappresenta un raggruppamento di eventi correlati che devono essere gestiti in termini di processo. In questo modo, al tuo Auction viene data la responsabilità di sorvegliare la coerenza di è proprio e quindi di poter utilizzare utilmente requisiti aziendali astratti.

Ciò sfocia nel secondo problema di non separare le responsabilità nelle entità corrette. Il problema che descrivi è basato sul tuo Auction che deve "chiedere" un saldo dell'account. Chiaramente, dovrebbe essere la responsabilità del tuo Account ( UserBalance è un nome confuso) per tenere traccia del saldo attuale. Pertanto, deve anche essere responsabile della creazione di un nuovo Bid . In questo modo, possiamo separare in modo pulito la logica necessaria per verificare un saldo del conto da quello delle offerte. Nessuno in un'asta si occupa dei saldi dei conti, quindi teniamolo separato dalla nostra asta. Pensa in questo modo, "come funziona un'asta?". Può essere modellato in modo relativamente semplice:

OfferBidHandler (sposta i soldi nel deposito di garanzia, crea Bid ):

// throws NotFound
account = accounts.Find( cmd.UserId ) 

// throws InsuficientFunds or raises 'BidOffered'
account.OfferBid( cmd.AuctionId, cmd.LotNumber, cmd.Amount ) 

PlaceBidHandler (tiene traccia di tutto Bids o forse solo Bid più alto):

// throws NotFound
auctioneer = auctioneers.Find( cmd.AuctionId ) 

// raises Outbidded
auctioneer.ReceiveCompetingBid( cmd.UserId, cmd.LotNumber, cmd.Amount ) 

RevokeBidHandler (rimuove i soldi dall'impegno):

// throws NotFound
account = accounts.Find( cmd.UserId ) 

account.RevokeBid( cmd.AuctionId, cmd.LotNumber ) 

Il Auction (che mediates il processo) è semplicemente responsabile di reagire agli eventi BidOffered in modo da attivare PlaceBid OR RevokeBid comandi (se NotFound viene lanciata) e reagisci agli eventi Outbidded in modo da attivare i comandi RevokeBid .

Se vuoi mantenere Account e Auction in contesti separati (non vuoi Account.OfferBid ), questo può essere ulteriormente modificato per aggiungere un ulteriore livello lungo le linee di: PlaceOrder -> PaymentReceived -> PlaceBid (in questo caso che ordina un Bid e un conto di addebito) e BidRevoked -> RefundPayment per separare in modo pulito i conti dalle aste.

    
risposta data 20.11.2018 - 20:52
fonte