Domain Driven Design e interazione tra domini


10

Sono un principiante relativamente DDD, ma sto leggendo qualsiasi cosa e tutto ciò su cui riesco a mettere le mani a bollire e distillare le mie conoscenze.

Mi sono imbattuto in questa domanda DDD e una delle risposte mi ha incuriosito.

DDD Contesti e domini limitati?

In una delle risposte il poster fornisce l'esempio di un sistema di e-commerce con prodotti in almeno 2 domini:

1) Catalogo prodotti 2) Gestione delle scorte

OK, tutto ha un senso, vale a dire nel tuo front-end e-commerce sei interessato a visualizzare le informazioni sul prodotto e non interessato alla gestione dell'inventario.

MA. È possibile che si desideri visualizzare il livello di inventario sulla pagina Web oppure visualizzare il numero di edizione dell'inventario in magazzino (immaginando che l'inventario sia costituito da libri, riviste, ecc.). Questa informazione proviene dal dominio di Inventory.

Quindi, come lo gestiresti? Vorresti

a) Caricare sia il dominio del prodotto che gli aggregati del dominio dello spazio pubblicitario? b) Manterresti alcune proprietà sull'entità del dominio del prodotto per il numero in stock e l'edizione in stock, quindi utilizzeresti Eventi di dominio per aggiornarli quando l'entità Inventory viene aggiornata?

Un'ultima domanda. So che dobbiamo dimenticare / ignorare la persistenza del dominio e pensare solo al dominio. Ma solo per pensarci bene, nell'esempio sopra finiremmo con potenzialmente 2 tabelle DB per il catalogo dei prodotti e l'inventario dei prodotti. Ora, usiamo lo stesso identificatore in questi in quanto è lo stesso prodotto. Oppure, potremmo usare 1 tabella e 1 riga di tabella per i dati e semplicemente mappare i dati rilevanti sulle proprietà aggregate?

Risposte:


8

È possibile che si desideri visualizzare il livello di inventario sulla pagina Web oppure visualizzare il numero di edizione dell'inventario in magazzino (immaginando che l'inventario sia costituito da libri, riviste, ecc.). Questa informazione proviene dal dominio di Inventory.

La cosa principale da notare a questo punto è che stai parlando di una vista, vale a dire che l'uso di dati non aggiornati è accettabile.

Detto questo, non è necessario interagire con gli aggregati (che sono responsabili della prevenzione delle modifiche che violano l'invariante aziendale), ma con una rappresentazione di una copia recente dello stato dell'aggregato.

Quindi quello che mi aspetterei normalmente è una query eseguita sul Catalogo prodotti, un'altra eseguita sull'Inventario e qualcosa per comporre i due nel DTO di cui hai bisogno per supportare la vista.

Caricare sia il dominio del prodotto che gli aggregati del dominio dello spazio pubblicitario?

Quindi è vicino . Non abbiamo bisogno di caricare gli aggregati, perché non cambieremo nulla. Ma abbiamo bisogno del loro stato; così potremmo caricarlo. Detto questo, normalmente mi aspetto che i due domini vengano eseguiti in processi diversi. Pertanto, chiameremmo entrambi, non caricando entrambi.

Conservereste alcune proprietà sull'entità del dominio del prodotto per il numero in stock e l'edizione in stock, quindi usereste gli Eventi di dominio per aggiornarli quando l'entità Inventory viene aggiornata?

"Non attraversare i corsi d'acqua. Sarebbe male."

Utilizzare gli eventi per coordinare le informazioni nei contesti di dominio: ottima idea. Spingere concetti che appartengono a un dominio in un altro: opposto di una grande idea, tranne che di più.

Vuoi mantenere puliti i domini. Le applicazioni che interagiscono con i domini non sono così importanti. Ad esempio, è ragionevole che l'applicazione Inventory chiami un servizio nell'applicazione del prodotto per interrogare alcuni concetti specifici del prodotto da aggiungere a una vista. O vice versa.

Non conosco alcun motivo per cui una singola applicazione deve essere limitata a un singolo dominio. Finché esiste un'unica fonte di verità, puoi distribuire le transazioni nel modo che preferisci.

Ma solo per pensarci bene, nell'esempio sopra finiremmo con potenzialmente 2 tabelle DB per il catalogo dei prodotti e l'inventario dei prodotti. Ora, usiamo lo stesso identificatore in questi in quanto è lo stesso prodotto.

Sarebbe il modo più semplice. In termini più grandi, si utilizza lo stesso identificatore perché l'entità del mondo reale è la stessa; i due diversi contesti limitati modellano quell'entità in modo diverso, ma il modello non è l'entità del mondo reale.

Quando non funziona, avrai bisogno di alcune query da utilizzare per colmare il divario. Penso che la variante più comune di ciò sia che l'entità più recente conserva l'id dell'entità più vecchia. Lo vedrai anche all'interno di un singolo BC: i candidati, una volta approvati, diventano clienti. È un aggregato diverso (lo stato associato a un client è soggetto a un invariante diverso da quello del richiedente); quindi se il livello di persistenza utilizza flussi di eventi, il flusso per il nuovo aggregato avrà bisogno di un identificatore diverso. Quindi ci sarà un po 'di stato da qualche parte che dice "questo candidato è diventato questo cliente".

Oppure, potremmo usare 1 tabella e 1 riga di tabella per i dati e semplicemente mappare i dati rilevanti sulle proprietà aggregate?

YIKES! No, non farlo. Stai aggiungendo il conflitto di transazione senza alcun motivo commerciale per farlo.


Ho indicato questa come risposta, merito anche a @guillaume di seguito che ha anche sottolineato che la lettura dei dati da visualizzare in una vista non richiede il caricamento degli aggregati. Grazie per una risposta così lunga e dettagliata. Provenendo dal primo approccio al modello di dati "tradizionale", ho trovato difficile forzare me stesso a dimenticare il livello di persistenza e concentrarmi sul linguaggio di dominio. Proprio quando penso di averlo capito, ho letto un altro articolo che fa saltare la mia comprensione.
PendorPaul

Ho appena letto questo articolo msdn.microsoft.com/en-us/magazine/dn802601.aspx che descrive in dettaglio l'utilizzo di Domain Events per duplicare alcuni dati tra contesti. L'esempio è la condivisione di un elenco di clienti tra il servizio clienti e un sistema di elaborazione degli ordini. Si tratta di duplicare i dati dei clienti nel sistema degli ordini e di utilizzare Eventi per sincronizzare i dati. Questo vola di fronte a ciò che abbiamo risposto qui. Sicuramente nell'esempio collegato il contesto dell'ordine richiede solo un ID cliente, che può essere popolato dall'applicazione che ha accesso al contesto del servizio clienti.
PendorPaul

Ti consigliamo di essere molto preciso nei tuoi pensieri, qui. Copiare i dati tra i sistemi NON è la stessa cosa che copiare i dati tra i modelli di dominio. Ogni volta che vedi "sola lettura [mumble]", è un grande suggerimento che i dati non appartengono a quel dominio (anche se l'applicazione potrebbe ancora interessarsene).
VoiceOfUnreason

Sì, è quello che ho pensato anch'io. Il cambiamento del processo di pensiero è abbastanza difficile senza "esperti" che pubblicano articoli che confondono le acque. Ho speso oggi analizzando davvero il mio dominio, per cercare di comprendere appieno dove si trovano le mie radici BC e aggregate. Sono praticamente lì, ma so anche che posso perfezionare e riformattare il mio modello mentre vado. Sono stato bloccato nell'analisi infernale per giorni ormai, le preoccupazioni per iniziare a scrivere codice temendo che non ho DDD dritto nella mia testa. Penso che sia meglio andare avanti e continuare a mettere in discussione il mio codice e se il modello è giusto. Ci arrivo. Grazie
PendorPaul,

Forse ho letto male, ma la pratica con cui credo che tu stia parlando si chiama Event-Carried State Transfer (Event-Carried State Transfer) e può essere un modello molto potente soprattutto per le viste del cruscotto / tabella in cui è necessario filtrare i dati della pagina attraverso i servizi. È possibile creare "proiezioni" (sostanzialmente visualizzazioni) da eventi di dominio per guidare questi dashboard. L'ho già usato abbastanza bene. Può essere uno strumento molto potente nel giusto scenario. IMHO, ciò che è più importante qui è che ti rendi conto che queste proiezioni non rappresentano modelli di dominio e non dovrebbero contenere la logica aziendale.
Giordania,

3

Penso che la tua domanda richieda davvero 2 set ortogonali di opzioni -

  • Carichi due oggetti e presenti i loro dati insieme o carichi 1 oggetto che contiene tutto ciò che desideri?

  • Usi aggregati per visualizzare elementi o qualcos'altro?

Se credi nell'approccio CQRS, risulta che gli aggregati potrebbero non essere la scommessa migliore per le letture. Ogni volta che carichi un aggregato, sia per visualizzare i suoi dati o modificarli, aggiungi concorrenza e contesa al tuo sistema. Inoltre, gli aggregati sono potenzialmente più voluminosi e più lenti da caricare rispetto a quando si utilizzano modelli di lettura ad hoc su misura per la visualizzazione.

La soluzione a) della tua Q sembra soggetta a molte di queste insidie. L'opzione b) può essere valida, ma la userei solo se i dati del InventoryManagementBC sono necessari per imporre gli invarianti durante la mutazione Productdell'aggregato. È meglio se un aggregato contiene tutti i dati necessari per verificare le sue regole aziendali in caso di modifica, ma sul lato di lettura possono sedere ovunque.

Per quanto riguarda i dati, una raccomandazione comune è quella di fornire il proprio database Bounded Contexts (per motivi di implementazione e SoC). Probabilmente dovrai usare gli stessi identificatori se vuoi abbinare i prodotti tra i due BC.

A proposito delle interazioni cross-BC, potresti anche dare un'occhiata a /programming/16713041/communicating-between-two-bounded-contexts-in-ddd


1
OK, questo aiuta. Sostanzialmente stiamo usando Aggregate Roots e BC per garantire che i nostri invarianti siano coerenti e che i nostri dati siano validi e che le operazioni che vogliamo eseguire siano racchiuse nel nostro aggregato o BC. Quando si tratta di leggere e visualizzare quei dati, possiamo gestirli separatamente e leggeri senza che tutti gli aggregati / BC siano idratati. Dopotutto, perché dobbiamo caricare l'aggregato quando tutto ciò che stiamo facendo è visualizzare i dati in un report o sullo schermo. Questo ha perfettamente senso. Grazie.
PendorPaul

È qui che CQRS brilla davvero. È possibile eseguire una query SQL, unire le tabelle e restituire la query personalizzata per una vista specifica. Cancella anche il tuo repository dal pasticcio del metodo di query. Inoltre, puoi persino replicare i dati sul servizio come ElasticSearch e interrogarli.
importa il

1

DDD è pensato per applicazioni in cui la logica aziendale è complessa. "stampare qualcosa" non è una logica aziendale complessa. In realtà non è affatto una logica aziendale.

Se la logica aziendale in un contesto richiede alcune informazioni per gestire correttamente alcuni casi d'uso, tali informazioni fanno parte di quel contesto. Quindi l'idea che un contesto limitato potrebbe aver bisogno di informazioni disponibili in diversi contesti limitati non ha senso, perché il contesto limitato ha tutte le informazioni di cui ha bisogno.


OK, quindi prendi qualcosa come Amazon, che è un sistema complesso con una logica aziendale complessa. Hanno la gestione del catalogo e la gestione dell'inventario. Non hanno bisogno di totali di inventario per gestire il catalogo, con questo intendo il nome, la descrizione, il tipo, la condizione del prodotto, ecc. Tuttavia, mostrano il numero di articoli in magazzino nella prima pagina del negozio. In quello scenario, in cui si desidera separare i domini di gestione del catalogo prodotti e inventario dei prodotti, ma è necessario visualizzare alcune informazioni sull'inventario nella pagina del prodotto, come si fa?
PendorPaul

Immagino che ciò che sto dicendo sia che il dominio del catalogo prodotti ha tutte le informazioni necessarie per gestire il catalogo prodotti. Il dominio di inventario ha tutte le informazioni necessarie per gestire l'inventario. Tuttavia, quando voglio mostrare alcune informazioni a un utente e quei dati provengono da 2 domini, dove posso farlo. Devo solo caricare entrambi i domini nella mia IU e associare le proprietà che voglio mostrare? Oppure ho un meccanismo di segnalazione / lettura in cui restituisco un tipo anonimo o DTO contenente i dati necessari per la mia interfaccia utente?
PendorPaul

È comico guardare indietro a questi miei vecchi commenti 11 mesi fa, ma sembra una vita. Avevi perfettamente ragione Euforico sull'aspetto di lettura e scrittura. L'applicazione a cui sto lavorando si è evoluta un po 'man mano che la mia comprensione si è evoluta. Ora sto usando i metodi CQRS, attraverso Jimmy Bogards Mediatr. La straordinaria flessibilità di avere comandi che interagiscono con gli aggregati, ma poi usare query e gestori di query per riportare tutto ciò di cui ho bisogno per la visualizzazione è incredibile. Riempilo in viste che richiamano quelle QueryHandlers e il disaccoppiamento è buono con questo. Grazie
PendorPaul il

@PendorPaul, penso di essere dove eri 11 (ora 13) mesi fa. Cosa ti ha portato dove sei adesso?
Greg Bell,

1
@GregBell Devi forzare un cambiamento di mentalità. Ero bloccato nell'approccio di "progettare un database, costruire un livello di dati, costruire una logica aziendale ... ecc.". E mi sono concentrato sulla creazione di tutte le entità comprendenti. vale a dire un "Prodotto" in un sito di e-commerce, che ha gestito tutto da prezzi, descrizione, inventario, posizione di magazzino .... ma che diventa estremamente complesso. L'approccio contestuale limitato significa che nel contesto di Inventario, il "Prodotto" contiene solo informazioni e comportamenti per la gestione dell'inventario. Descrizione e immagini sono gestite in un contesto di contenuto, prezzi nel contesto dei prezzi.
PendorPaul

1

Dal mio punto di vista ci sono diverse definizioni di "Prodotto" - ogni contesto limite ha una sua definizione di "prodotto" -dominio:

  • Nel Content-Management-Bounding-Context un prodotto ha un'immagine e un testo descrittivo.
  • Nell'Inventario-Bounding-Context un prodotto ha quantità-scorte, venditore del prodotto, previsioni quando il prodotto sarà disponibile
  • Nel contesto del prezzo-caculazione-delimitazione ci sono delle regole sul costo di un prodotto per quantità.

Inoltre, aggiungerei un ulteriore contesto per i confini del negozio con la sua definizione del prodotto (una combinazione pertinente dei domini del prodotto degli altri contesti del confine).

Un Negozio-Prodotto avrebbe "immagine e testo descrittivo" dal contenuto e disponibilità da "Inventario" ma non da "venditore del prodotto" dall'inventario.

Questo ulteriore contesto di limitazione del negozio dipende dal contenuto, dall'inventario e dal prezzo del contesto di limitazione


Quindi, come stai creando quel negozio-prodotto BC? In quel contesto hai un riferimento al BC prodotto e inventario e stai idratando quelli dal tuo negozio di persistenza quando carichi un BC negozio-prodotto e poi offri le proprietà che desideri da quei BC attraverso le proprietà StoreProduct? Ho trovato questo articolo che è in linea con quello che stavo pensando, attraversando eventi BC ma sopra @ guillaume13 ha sottolineato che ai fini della visualizzazione posso evitare il BC e ritirare semplicemente i dati di cui ho bisogno per la mia vista. msdn.microsoft.com/en-us/magazine/dn802601.aspx
PendorPaul
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.