2010-03-17 1 views
5

Durante la ricerca sul web, mi sono imbattuto in un elenco di regole da libro di Eric Evans' che dovrebbe essere applicata per gli aggregati:Come devono essere applicate le regole per i Root aggregati?

  1. L'Entità radice ha identità globale ed è in ultima analisi responsabile del controllo invarianti
  2. Entità Root avere identità globale. Le entità all'interno del confine hanno un'identità locale, unica solo all'interno dell'Agregato.
  3. Nulla al 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).
  4. È possibile ottenere solo i root aggregati direttamente con le query del database. Tutto il resto deve essere fatto attraverso l'attraversamento.
  5. Gli oggetti all'interno di Aggregate possono contenere riferimenti ad altre radici Aggregate.
  6. Un'operazione di eliminazione deve rimuovere tutto all'interno del limite Aggrega tutto in una volta
  7. 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.

questo tutto sembra bene in teoria, ma non vedo come queste regole sarebbero applicate nel mondo reale.

Prendere la regola 3 per esempio. Una volta che l'entità radice ha assegnato a un oggetto esterno un riferimento a un'entità interna, che cosa significa mantenere quell'oggetto esterno trattenendo il riferimento oltre il singolo metodo o blocco?

(Se l'applicazione di questo è specifico per la piattaforma, sarei interessato a sapere come questo possa essere applicata all'interno di un C# /. NET ambiente/NHibernate.)

+0

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

+0

@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

risposta

-1

mio modo preferito di far rispettare i modelli e le pratiche DDD educa costantemente le persone sul loro valore. Ci sono tuttavia momenti in cui io con I ho avuto uno strumento più rigoroso.

Non l'ho ancora fatto, ma mi sembra che FluentNHibernate potrebbe essere un buon strumento per applicare le proprietà di aggregazione.

L'esempio può essere implementato contrassegnando tutte le radici aggregate con l'interfaccia marker "IAggregateRoot" e le entità non root con l'interfaccia marker "IEntity". Quindi, la convenzione FNH personalizzata controllerebbe le entità contrassegnate come IEntity che fa riferimento alle entità IEntity e, quando rilevate, segnalerebbe un errore (ad esempio, genera un'eccezione).

Ha senso?

+1

Contrassegnare le entità radice aggregate con IAggregateRoot e altre entità con IEntity potrebbe essere utile per controllare quali entità possono essere utilizzate con il modello di repository, ma non sono sicuro che ciò sarebbe efficace nell'imporre le buone pratiche in questa situazione per due motivi. 1) Le entità aggregate possono contenere riferimenti ad altri oggetti al di fuori dei confini aggregati (solo non il contrario). Questo sarebbe quindi un uso valido di un IEntity che fa riferimento a un IEntity. 2) Se si utilizzano entità diverse come radici aggregate in diversi casi d'uso, l'applicazione potrebbe non essere accurata. – MylesRip

+0

Sembra, quindi, che "far rispettare le regole" per le radici aggregate sia meno una questione di definizione delle entità in modo tale che le regole ** debbano ** essere seguite, e più una questione di educazione e vigilanza eterna. Sarebbe una dichiarazione giusta? – MylesRip

6

Non penso che dovresti consentire all'aggregato di dare accesso al codice esterno alle sue entità.

Tu dici al tuo aggregato cosa vuoi che accada e lo gestisce.

Se abbiamo un aggregato: auto. Non ci importa della benzina e delle ruote, guidiamo. Chiediamo alla macchina cose e risponde senza dare riferimenti agli interni.

Chiediamo: abbiamo benzina? Sì. Non: dammi l'oggetto cisterna, così posso controllare se abbiamo benzina.

+1

Quindi, se stai recuperando solo i dati a cui sono state applicate alcune regole dopo essere uscito dal database. Se dovessi nascondere le entità, l'unica cosa rimasta è quella di esporre le proprietà necessarie attraverso la radice, una alla volta. Se ci sono diverse entità, questo suona disordinato. Non sono in disaccordo, sto cercando di trovare le migliori pratiche man mano che apprendo questo concetto. – Sinaesthetic

+0

Il mio primo pensiero su quello non sarebbe solo quello di recuperare i dati e applicare le regole ma di ottenere i dati attraverso l'oggetto che userebbe i dati. L'oggetto applicherebbe quindi le regole. Penso che dovresti considerare ogni azione dall'alto verso il basso. Qual è l'obiettivo, come si avvolgere questa richiesta. Esempio: diciamo che abbiamo un sistema per verificare se una persona ha una certa età e quindi aprire una porta se è vero. Chiameremo Door.open (Persona) Questo metodo gestirà quindi tutte le subquery e le chiamate senza esporre i dati. –

0

Tecnicamente non penso che ci sia un modo per impedire ad un oggetto esterno di mantenere il riferimento oltre un singolo metodo o blocco. Immagino che devi solo forzare questa regola nel tuo progetto.

0

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.

0

Una cosa che potresti fare è dare una copia dello stato interno al mondo esterno.

Problemi correlati