it-swarm.it

DDD Aggregates è davvero una buona idea in un'applicazione Web?

Mi sto tuffando nel Domain Driven Design e alcuni dei concetti che sto incontrando hanno molto senso in superficie, ma quando ci penso di più, mi chiedo se sia davvero una buona idea.

Il concetto di aggregati, ad esempio, ha un senso. Crei piccoli domini di proprietà in modo da non dover gestire l'intero modello di dominio.

Tuttavia, quando ci penso nel contesto di un'app Web, spesso colpiamo il database per ritirare piccoli sottogruppi di dati. Ad esempio, una pagina può elencare solo il numero di ordini, con collegamenti su cui fare clic per aprire l'ordine e vedere l'id del suo ordine.

Se sto capendo bene gli aggregati, in genere utilizzerei il modello di repository per restituire un OrderAggregate che conterrebbe i membri GetAll, GetByID, Delete e Save. Ok suona bene. Ma...

Se chiamo GetAll per elencare tutti i miei ordini, mi sembra che questo modello richiederebbe la restituzione dell'intero elenco di informazioni aggregate, gli ordini completi, le righe dell'ordine, ecc ... Quando ho bisogno solo di un piccolo sottoinsieme di tali informazioni (solo informazioni sull'intestazione).

Mi sto perdendo qualcosa? O c'è qualche livello di ottimizzazione che useresti qui? Non riesco a immaginare che qualcuno sosterrebbe la restituzione di interi aggregati di informazioni quando non ne hai bisogno.

Certamente, si potrebbero creare metodi sul proprio repository come GetOrderHeaders, ma ciò sembra vanificare lo scopo di utilizzare un modello come repository in primo luogo.

Qualcuno può chiarire questo per me?

EDIT:

Dopo molte più ricerche, penso che la disconnessione qui sia che un modello di Repository puro è diverso da quello che la maggior parte delle persone considera un Repository come essere.

Fowler definisce un repository come un archivio dati che utilizza la semantica della raccolta e viene generalmente tenuto in memoria. Questo significa creare un intero oggetto grafico.

Evans modifica il repository in modo da includere le radici degli aggregati, e quindi il repository viene amputato per supportare solo gli oggetti in un aggregato.

La maggior parte delle persone sembra pensare ai repository come a oggetti di accesso ai dati glorificati, in cui si creano semplicemente metodi per ottenere qualsiasi dato desiderato. Questo non sembra essere lo scopo descritto in Patterns of Enterprise Application Architecture di Fowler.

Altri ancora considerano un repository come una semplice astrazione utilizzata principalmente per rendere più facili i test e le derisioni o per disaccoppiare la persistenza dal resto del sistema.

Immagino che la risposta sia che questo è un concetto molto più complesso di quanto pensassi inizialmente.

43
Erik Funkenbusch

Non utilizzare il modello di dominio e gli aggregati per le query.

In effetti, quello che stai ponendo è una domanda abbastanza comune che è stata stabilita una serie di principi e modelli per evitare proprio questo. Si chiama CQRS .

31
quentin-starin

Ho faticato, e sto ancora lottando, su come utilizzare al meglio il modello di repository in un Domain Driven Design. Dopo averlo usato per la prima volta, ho escogitato le seguenti pratiche:

  1. Un repository dovrebbe essere semplice; è responsabile solo della memorizzazione degli oggetti di dominio e del loro recupero. Tutte le altre logiche dovrebbero trovarsi in altri oggetti, come fabbriche e servizi di dominio.

  2. Un repository si comporta come una raccolta come se fosse una raccolta in memoria di radici aggregate.

  3. Un repository non è un DAO generico, ogni repository ha la sua interfaccia unica e ristretta. Un repository ha spesso specifici metodi Finder che ti consentono di cercare nella raccolta in termini di dominio (ad esempio: dammi tutti gli ordini aperti per l'utente X). Il repository stesso può essere implementato con l'aiuto di un DAO generico.

  4. Idealmente, i metodi Finder restituiranno solo radici aggregate. Se questo è inefficiente, può anche restituire oggetti valore di sola lettura piuttosto che contenere esattamente ciò di cui hai bisogno (anche se è un vantaggio se questi oggetti valore possono anche essere espressi in termini di dominio). Come ultima risorsa, il repository può essere utilizzato anche per restituire sottoinsiemi o raccolte di sottoinsiemi di una radice aggregata.

  5. Le scelte come queste dipendono dalle tecnologie utilizzate, in quanto è necessario trovare un modo per esprimere in modo più efficiente il proprio modello di dominio con le tecnologie utilizzate.

8
Kdeveloper

Non credo che il tuo metodo GetOrderHeaders sconfigga affatto lo scopo del repository.

DDD si preoccupa (tra le altre cose) di assicurarti di ottenere ciò di cui hai bisogno tramite la radice aggregata (non avresti un OrderDetailsRepository, per esempio), ma non ti limita nel modo in cui stai citando.

Se un OrderHeader è un concetto di dominio, è necessario averlo definito come tale e disporre dei metodi di repository appropriati per recuperarli. Assicurati solo di passare attraverso la radice aggregata corretta quando lo fai.

6
Eric King

Il mio uso di DDD non può essere considerato DDD "puro", ma ho adattato le seguenti strategie del mondo reale usando DDD rispetto a un archivio dati DB.

  • Una radice aggregata ha un repository associato
  • Il repository associato viene utilizzato solo da quella radice aggregata (non è pubblicamente disponibile)
  • Un repository può contenere chiamate di query (ad esempio GetAllActiveOrders, GetOrderItemsForOrder)
  • Un servizio espone un sottoinsieme pubblico del repository e altre operazioni non crude (ad esempio, trasferire denaro da un conto bancario a un altro, LoadById, Search/Find, CreateEntity, ecc.).
  • Uso il root -> Servizio -> Stack repository. Un servizio DDD suppone di essere utilizzato solo per qualsiasi cosa a cui un'entità non possa rispondere (ad esempio LoadById, TransferMoneyFromAccountToAccount), ma nel mondo reale tendo ad attaccare anche altri servizi correlati a CRUD (Salva, Elimina, Query) anche se il root dovrebbe essere in grado di "rispondere/eseguire" questi stessi. Nota che non c'è nulla di sbagliato nel dare a un'entità l'accesso a un altro servizio radice aggregato! Tuttavia, ricorda che non includeresti in un servizio (GetOrderItemsForOrder) ma lo includeresti nel repository in modo che la radice aggregata possa farne uso. Si noti che un servizio non dovrebbe esporre alcuna query aperta come il repository può.
  • Di solito definisco un repository in modo astratto nel modello di dominio (tramite interfaccia) e fornisco un'implementazione concreta separata. Definisco pienamente un servizio nel modello di dominio iniettando in un repository concreto per il suo utilizzo.

** Non è necessario ripristinare un intero aggregato. Tuttavia, se vuoi di più devi chiedere al root, non ad altri servizi o repository. Questo è un caricamento pigro e può essere fatto manualmente con un caricamento pigro di un uomo povero (iniettando il repository/servizio appropriato nella radice) o usando e ORM che lo supporta.

Nel tuo esempio, probabilmente fornirei una chiamata al repository che ha portato solo le intestazioni dell'ordine se volessi caricare i dettagli su una chiamata separata. Nota che disponendo di un "OrderHeader" stiamo effettivamente introducendo un concetto aggiuntivo nel dominio.

4
Mike Rowley

Il tuo modello di dominio contiene la tua logica aziendale nella sua forma più pura. Tutte le relazioni e le operazioni a supporto delle operazioni aziendali. Quello che ti manca nella tua mappa concettuale è l'idea di Application Service Layer il livello di servizio si avvolge attorno al modello di dominio e fornisce una visione semplificata del dominio aziendale (una proiezione se vuoi) che consente il modello di dominio da modificare in base alle esigenze senza influire direttamente sulle applicazioni utilizzando il livello di servizio.

Andare avanti. L'idea dell'aggregato è che esiste un oggetto, la radice aggregata, responsabile del mantenimento della coerenza dell'aggregato. Nel tuo esempio, l'ordine sarebbe responsabile della manipolazione delle righe dell'ordine.

Nel tuo esempio, il livello di servizio espone un'operazione come GetOrdersForCustomer che restituisce solo ciò che è necessario per visualizzare un elenco riepilogativo degli ordini (come li chiami OrderHeaders).

Infine, il modello di repository non è SOLO una raccolta, ma consente anche query dichiarative. In C # è possibile utilizzare LINQ come oggetto query , oppure la maggior parte degli altri O/RM fornisce anche una specifica oggetto query.

Un repository media tra i livelli di mappatura dei dati e del dominio, agendo come una raccolta di oggetti di dominio in memoria. Gli oggetti client costruiscono le specifiche della query in modo dichiarativo e le inviano al repository per soddisfazione. (da pagina del repository di Fowler )

Visto che è possibile creare query sul repository, ha anche senso fornire metodi convenienti che gestiscono query comuni. Cioè se vuoi solo le intestazioni del tuo ordine, puoi creare una query che restituisca solo l'intestazione ed esporla da un metodo conveniente nei tuoi repository.

Spero che questo aiuti a chiarire le cose.

3
Michael Brown

So che questa è una vecchia domanda, ma sembra che sia arrivata una risposta diversa.

Quando creo un repository, in genere vengono inserite alcune query memorizzate nella cache.

Fowler definisce un repository come un archivio dati che utilizza la semantica della raccolta e viene generalmente tenuto in memoria. Questo significa creare un intero oggetto grafico.

Conserva i repository nella RAM dei tuoi server. Non passano semplicemente attraverso gli oggetti nel database!

Se utilizzo un'applicazione Web con una pagina che elenca gli ordini, è possibile fare clic su per visualizzare i dettagli, è probabile che desidererò che la mia pagina della lista degli ordini abbia dettagli sugli ordini (ID, Nome, Importo, Data) per aiutare un utente a decidere quale desidera guardare.

A questo punto hai due opzioni.

  1. È possibile eseguire una query sul database e ritirare esattamente ciò che è necessario per creare l'elenco, quindi eseguire nuovamente una query per estrarre i singoli dettagli che è necessario visualizzare nella pagina dei dettagli.

  2. È possibile effettuare 1 query che recupera tutte le informazioni e le memorizza nella cache. Nella richiesta della pagina successiva leggi dalla RAM del server invece che dal database. Se l'utente torna indietro o seleziona la pagina successiva, stai ancora effettuando zero viaggi nel database.

In realtà il modo in cui lo implementate è proprio questo e i dettagli dell'implementazione. Se il mio più grande utente ha 10 ordini, probabilmente voglio andare con l'opzione 2. Se sto parlando di 10.000 ordini, allora è necessaria l'opzione 1. In entrambi i casi precedenti e in molti altri casi, desidero che il repository nasconda tali dettagli di implementazione.

Andando avanti se ricevo un biglietto per dire all'utente quanto hanno speso per gli ordini ( dati aggregati ) nell'ultimo mese nella pagina di elenco degli ordini, preferirei scrivere la logica per calcolarla in SQL e fare ancora un altro round trip nel DB o preferiresti calcolarla usando i dati che sono già nella RAM dei server?

Nella mia esperienza, gli aggregati di domini offrono enormi vantaggi.

  • Sono un enorme riutilizzo del codice parte che funziona davvero.
  • Semplificano il codice mantenendo la logica di business nel livello principale anziché dover eseguire il drill through su un livello di infrastruttura per far sì che il server sql lo faccia.
  • Possono inoltre velocizzare notevolmente i tempi di risposta riducendo il numero di query che è necessario effettuare poiché è possibile memorizzarle facilmente nella cache.
  • L'SQL che sto scrivendo è spesso molto più gestibile poiché spesso sto solo chiedendo tutto e calcolando il lato server.
0
WhiteleyJ