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


40

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 io sono Aggregati giusta comprensione, vorrei in genere utilizzare il modello di repository per restituire un OrderAggregate che dovrebbe contenere 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 di 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 questo sembra vanificare lo scopo di utilizzare un modello come repository in primo luogo.

Qualcuno può chiarire questo per me?

MODIFICARE:

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 l'intento descritto in Patterns of Enterprise Application Architecture di Fowler.

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

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


4
"Immagino che la risposta sia che questo è un concetto molto più complesso di quanto pensassi inizialmente." Questo è molto vero
Quentin-starin,

per la tua situazione, potresti creare un proxy per l'oggetto radice aggregato che recupera e memorizza in modo selettivo i dati solo quando richiesto
Steven A. Lowe,

Il mio suggerimento è di implementare il carico lento nelle associazioni di aggregati root. Quindi puoi recuperare un elenco di root senza caricare troppi oggetti.
Juliano,

4
quasi 6 anni dopo, ancora una buona domanda. Dopo aver letto il capitolo del libro rosso, direi: non rendere i tuoi aggregati troppo grandi. È allettante scegliere un concetto di alto livello dal tuo dominio e dichiararlo Root to Rule Them All ma DDD sostiene aggregati più piccoli. E mitiga le inefficienze come quelle che descrivi sopra.
Cpt. Senkfuss,

I tuoi aggregati dovrebbero essere il più piccoli possibile pur essendo naturali ed efficaci per il dominio (una sfida!). Inoltre, è perfettamente corretto e auspicabile che i repository abbiano metodi altamente specifici .
Timo

Risposte:


30

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

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


2
@Mystere Uomo: No, è di fornire i dati minimi necessari. Questo è uno dei grandi scopi di un modello di lettura separato. Aiuta anche ad affrontare alcuni problemi di concorrenza in anticipo. CQRS ha diversi vantaggi se applicato a DDD.
Quentin-starin,

2
@Mystere: sono abbastanza sorpreso che ti mancherebbe che se leggessi l'articolo a cui mi sono collegato. Vedi la sezione intitolata "Query (reporting)": "Quando un'applicazione richiede dati ... questo dovrebbe essere fatto in una singola chiamata al livello Query e in cambio otterrà un singolo DTO contenente tutti i dati necessari ... La ragione di ciò è che i dati vengono normalmente interrogati molte volte più di quanto il comportamento del dominio venga eseguito, quindi ottimizzando ciò migliorerai le prestazioni percepite del sistema. "
Quentin-starin,

4
Ecco perché non useremmo un repository per leggere i dati in un sistema CQRS. Scriveremmo una semplice classe per incapsulare una query (usando qualunque tecnologia fosse conveniente o necessaria, spesso direttamente ADO.Net o Linq2Sql o SubSonic si adattano bene qui), restituendo solo (tutti) i dati necessari per l'attività a portata di mano, per evitare trascinamento dei dati attraverso tutti i livelli normali di un repository DDD. Useremmo un repository per recuperare un aggregato solo se volessimo inviare un comando al dominio.
Quentin-starin,

9
"Non riesco a immaginare che qualcuno sostenga la restituzione di interi aggregati di informazioni quando non ne hai bisogno." Sto cercando di dire che hai esattamente ragione con questa affermazione. Non recuperare un intero insieme di informazioni quando non sono necessarie. Questo è il vero nucleo di CQRS applicato a DDD. Non è necessario un aggregato per eseguire una query. Ottieni i dati attraverso un meccanismo diverso, quindi esegui ciò in modo coerente.
Quentin-starin,

2
@qes In effetti, la soluzione migliore è quella di non utilizzare DDD per le query (leggi) :) Ma si utilizza DDD nella parte Command, ovvero per archiviare o aggiornare i dati. Quindi ho una domanda per te, usi sempre Archivi con Entità quando devi aggiornare i dati nel DB? Diciamo che è necessario modificare solo un piccolo valore nella colonna (passare da un tipo di ordinamento), caricare ancora l'intera Entità nel livello App, cambiare un valore (proprietà) e quindi salvare nuovamente l'intera Entità nel DB? Un po 'troppo esagerato?
Andrew,

8

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 metodi di ricerca specifici 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.


È sicuramente un argomento complesso. È difficile trasformare la teoria in pratica soprattutto quando combina due teorie distinte e separate in un'unica pratica.
Sebastian Patten,

6

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.


Forse sto confondendo concetti qui, ma la mia comprensione del modello di repository è di disaccoppiare la persistenza dal dominio, usando un'interfaccia standard per la persistenza. Se devi aggiungere metodi personalizzati per una specifica funzionalità, sembra che si stia ricollegando nuovamente le cose.
Erik Funkenbusch,

1
Il meccanismo di persistenza è disaccoppiato dal dominio, ma non ciò che viene persistito. Se ti ritrovi a dire cose come "dobbiamo elencare qui le intestazioni degli ordini", devi modellare OrderHeader nel tuo dominio e fornire un modo per recuperarle dal tuo repository.
Eric King,

Inoltre, non rimanere bloccato sull'interfaccia standard per la persistenza. Non esiste un modello di repository generico che sarà sufficiente per tutte le possibili app. Ogni app avrà molti metodi di repository oltre lo standard "GetById", "Save", ecc. Questi metodi sono il punto iniziale, non il punto finale.
Eric King,

4

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 es. 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 completamente 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. Si noti che avendo un "OrderHeader" stiamo effettivamente introducendo un concetto aggiuntivo nel dominio.


3

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 dalla tua mappa concettuale è l'idea del livello di servizio applicazione che il livello di servizio avvolge attorno al modello di dominio e fornisce una visione semplificata del dominio aziendale (una proiezione se vuoi) che consente al modello di dominio di cambiare secondo necessità 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 o la maggior parte degli altri O / RM fornisce anche una specifica oggetto query.

Un repository media 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. (dalla pagina del repository di Fowler )

Visto che è possibile creare query sul repository, ha anche senso fornire metodi di convenienza che gestiscano query comuni. Vale a dire se vuoi solo le intestazioni del tuo ordine, puoi creare una query che restituisce solo l'intestazione ed esporla da un metodo conveniente nei tuoi repository.

Spero che questo aiuti a chiarire le cose.


0

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

Quando creo un repository, in genere si esegue il wrapping di 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 ticket 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 calcolarlo in SQL e fare un altro round trip per il DB o preferiresti calcolarlo utilizzando i dati già presenti 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 invece di dover eseguire il drill through su un livello di infrastruttura per fare in modo 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.
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.