Come sostenerlo (o anche ciò che è possibile), penso, dipende in gran parte da come lo farete persiste. Ad esempio, stai usando NHibernate che penso significhi che tutto nell'oggetto dominio deve essere accessibile per poter essere mappato rispetto a Event Sourcing dove l'unica cosa che conta ricostruire lo stato dell'oggetto sono gli eventi stessi, che facilita la ricostruzione di oggetti interni che non sono accessibili tramite l'interfaccia pubblica.
- L'Entità radice ha identità globale ed è in ultima analisi responsabile del controllo invarianti Enti Root hanno identità globale. Le entità all'interno del confine hanno un'identità locale, unica solo all'interno dell'Agregato.
:: Uso il GUID per l'identità. Non usare mai un PK. Mai. Mi capita anche di usare GUID per qualsiasi entità non-root, ma potresti anche usare una stringa.
- Nulla di fuori del limite Aggregato può contenere un riferimento a qualsiasi cosa all'interno, tranne che all'entità radice. L'Entità radice può passare i riferimenti alle Entità interne ad altri oggetti, ma possono solo usarli in modo transitorio (all'interno di un singolo metodo o blocco).
:: Questo è il punto in cui l'implementazione della persistenza è importante. Sono di origine evento, posso usare gli eventi per ricostruire gli oggetti all'interno della radice che non sono accessibili dall'interfaccia pubblica della root. Quindi in C#, contrassegno semplicemente tutte le entità non-root come interne, e tutti gli accessi a quelli sono trasmessi tramite l'interfaccia pubblica della root. Poiché il mio dominio è nel suo stesso assembly, nessun client può mai ottenere un riferimento a entità non-root, né il compilatore mi consentirà di farlo accidentalmente. Se ho bisogno di esporre le proprietà, mi limito a garantire che siano di sola lettura/solo-get. Se stai usando un ORM, allora questo potrebbe essere impossibile, non sono sicuro. Se riesci a concedere l'accesso aa NHibernate, allora potresti aprire alcune porte, ma ti limiterà ancora in molti aspetti. In questo caso, la mia soluzione sarebbe quella di creare un paio di metodi che imitano l'istantanea dell'evento (ciò che avresti se fossi originario di un evento), che essenzialmente sputa uno stato contenente DTO che potrebbe essere utilizzato da NHibernate e accetta anche lo stesso DTO ripristinare lo stato sull'oggetto. Se possibile, assicurati che siano accessibili solo dal repository.
Dall'interno del dominio (oggetti di dominio che fanno riferimento ad altri oggetti di dominio), diventa semplicemente una disciplina (sottoposta a revisione del codice) che le entità non-root dovrebbero essere sempre presenti solo all'interno di root. Se si impostano correttamente gli spazi dei nomi, è possibile utilizzare la convalida delle dipendenze di Visual Studio per impedire la creazione del progetto quando questa regola viene violata.
- È possibile ottenere solo i root aggregati direttamente con le query del database. Tutto il resto deve essere fatto attraverso l'attraversamento.
:: Contrassegno le mie entità non root con IEntity che ha semplicemente un ID come parte dell'interfaccia. Quindi creo una classe astratta AggregateRoot che implementa IEntity. Questo obbedisce alla caratteristica "Una radice aggregata è un'entità all'interno dell'aggregato". Quindi i miei repository accettano o restituiscono solo istanze di AggregateRoot. Ciò è rafforzato dall'astrazione del repository, utilizzando i generici come vincoli, quindi non può essere violato sostanzialmente senza una certa shennaniganry.Vedere il commento successivo per "traversal"
- Gli oggetti all'interno di Aggregate possono contenere riferimenti ad altre radici Aggregate.
:: La parola chiave è "riferimenti". Questo significa solo un ID. Ad esempio, quando "aggiungi" un'istanza di RootB a RootA, RootA deve solo acquisire l'ID di RootB e viene salvato in questo modo. Quindi ora se hai bisogno di recuperare quello di RootB da RootA, devi chiedere a RootA di darti l'ID, quindi lo usi per cercare RootB in una query successiva.
- un'operazione di eliminazione deve rimuovere tutto entro i confini Aggregate tutto in una volta
:: Questo è piuttosto semplice, ma è anche molto dipendente dal business case. Ad esempio, diciamo che attraverso la radice, ho creato una configurazione. Come risultato della configurazione, sono stati creati diversi file di risorse. Se cancello la configurazione attraverso la radice, allora anche quei file di risorse dovrebbero essere cancellati. Nella maggior parte dei casi, se la persistenza di root è impostata correttamente, questo si prenderà cura di se stesso. Tuttavia, in termini di invarianti, potresti imbatterti in qualcosa di più complesso. Ad esempio, se si disponeva di un'entità manager che era una root e tale gestore aveva molti dipendenti che lo segnalavano, quindi eliminando il gestore, potrebbero essere necessarie molte azioni per completare il processo in termini commerciali. Ad esempio, forse quei dipendenti devono avere un campo "segnalazioni" annullato. Questo è un argomento più complicato perché sono coinvolti molti fattori di progettazione del sistema. Ad esempio, sei di origine evento, è un sistema basato su eventi o sincrono, ecc. Potrebbero esserci centinaia di modi diversi per risolvere questo problema. Penso che il punto principale qui sia che la radice di aggregazione è responsabile per assicurarsi che ciò accada, o almeno che il processo sia iniziato.
- Quando si esegue il commit di una modifica a qualsiasi oggetto all'interno del limite Aggregato, tutti gli invarianti dell'intero aggregato devono essere soddisfatti.
:: Vedere il commento precedente su dirigenti e dipendenti. Questo in pratica significa solo che prima di poter salvare la radice, tutte le regole aziendali devono essere state applicate. Applico ciò assicurandomi rispetto a quando esegui ActionA(), se una qualsiasi regola aziendale non riesce all'interno dell'aggregato o delle sue entità non-root, o oggetti value o QUALSIASI lungo la linea, allora lancio un'eccezione. Ciò impedisce il commit finale poiché l'Action() originale non ha mai completato. Affinché questo funzioni, è necessario assicurarsi che il gestore (qualunque cosa avvenga in questa azione) non provi a salvare prematuramente. Per emulare una transazione, di solito attendo fino alla fine dell'operazione (o catena di operazioni) prima di tentare di salvare qualcosa. Se il tuo contesto limitato è bello, devi solo salvare una singola entità (la radice) alla fine dell'operazione, poiché è la radice.
In alcuni casi è possibile salvare alcune radici, ma è necessario capire come eseguire il rollback della transazione. Quelle istantanee che ho menzionato potrebbero renderlo banale. Ad esempio, ottieni root A e B e salvi le loro istantanee (ricordi), quindi esegui l'operazione. Quindi si tenta di salvare RootA e supera. Si tenta di salvare RootB ma viene generata un'eccezione (forse la connessione non riesce o qualcosa del genere). Dopo alcuni tentativi falliti di logica, si utilizza l'istantanea per ripristinare RootB, quindi salvarla nuovamente, quindi ripetere l'eccezione in modo che venga visualizzata nei registri come un'eccezione irreversibile. Se per qualche motivo, non è possibile ripristinare e salvare RootA (il database è inattivo ora - timing scadente), è sufficiente registrare il memento con il log in modo che possa essere ripristinato manualmente in seguito (ad esempio, accodandolo per il ripristino). Ad alcuni non piace l'idea di lanciare eccezioni nel dominio per una violazione delle regole aziendali e sostengono che si dovrebbero usare eventi per questo (si veda: le eccezioni dovrebbero essere eccezionali) e non sono d'accordo. Sono semplicemente più a mio agio con questo approccio al momento.C'è un milione di modi in cui puoi farlo, ma non è una questione di DDD, sto solo offrendo alcune idee su come sfruttare il costrutto per risolvere quelle domande/problemi inevitabili.
So che questo è in ritardo di 8 anni, ma spero che aiuti qualcuno là fuori.
Un'altra domanda lungo le righe di sami relative alla regola n. 3 è se queste regole si applicano solo al livello dominio o applica anche i livelli applicazione o presentazione. Un esempio potrebbe utilizzare un ViewModel nel paradigma MVVM per racchiudere gli elementi interni a un aggregato per scopi di presentazione; questo viola la regola n. 3? – jpierson
@MylesRip Quando l'entità radice fornisce all'entità esterna un riferimento all'entità figlio interna, non fornisce alcun riferimento ad esso. Fornisce l'ID dell'entità figlio interna e solo l'ID. Esterno non può richiamare i metodi direttamente su quell'entità figlio interna. Deve andare nel repository, recuperare l'entità root e richiamare i metodi sull'entità root. – omittones