2011-02-07 9 views
5

Sono uno dei tanti che cerca di capire il concetto di radici aggregate e penso di averlo capito! Tuttavia, quando ho iniziato a modellare questo progetto di esempio, ho subito incontrato un dilemma.Semplice radice aggregata e repository

Ho le due entità ProcessType e Process. Un Process non può esistere senza uno ProcessType e uno ProcessType ha molti Process es. Quindi un processo contiene un riferimento a un tipo e non può esistere senza di esso.

Quindi dovrebbe ProcessType essere una radice aggregata? Verranno creati nuovi processi chiamando lo processType.AddProcess(new Process()); Tuttavia, ho altre entità che detengono solo un riferimento allo Process e accede al suo tipo tramite Process.Type. In questo caso non ha senso passare per lo ProcessType.

Tuttavia, le entità AFAIK esterne all'aggregazione possono contenere solo riferimenti alla radice dell'aggregato e non entità all'interno dell'aggregato. Quindi ho qui due aggregati, ciascuno con il proprio repository?

risposta

13

Sono in gran parte d'accordo con quello che ha detto Sisifo, in particolare il bit di non costrizione te stesso per le 'regole 'di DDD che può portare a una soluzione piuttosto illogica.

In termini di problema, ho incontrato la situazione molte volte e avrei definito "ProcessType" come una ricerca . Le ricerche sono oggetti che "definiscono" e hanno riferimenti no ad altre entità; nella terminologia DDD, sono oggetti valore. Altri esempi di ciò che definirei una ricerca potrebbero essere il "RoleType" di un membro del team, che potrebbe essere un tester, uno sviluppatore, un project manager per esempio. Anche di una persona 'titolo' che definirei una ricerca - Signor, signorina, la signora, il dottor

avrei modellare il vostro aggregato processo come:

public class Process 
{ 
    public ProcessType { get; } 
} 

Come dici tu, questo tipo di oggetti in genere hanno bisogno di per popolare i menu a discesa nell'interfaccia utente e quindi è necessario il proprio meccanismo di accesso ai dati. Tuttavia, NON ho personalmente creato "repository" come tali per loro, ma piuttosto un "LookupService". Questo per me mantiene l'eleganza del DDD mantenendo i "repository" rigorosamente per le radici aggregate.

Ecco un esempio di un gestore di comandi sul mio assistente di app e come ho implementato questo:

Team Member aggregata:

public class TeamMember : Person 
{ 
    public Guid TeamMemberID 
    { 
     get { return _teamMemberID; } 
    } 

    public TeamMemberRoleType RoleType 
    { 
     get { return _roleType; } 
    } 

    public IEnumerable<AvailabilityPeriod> Availability 
    { 
     get { return _availability.AsReadOnly(); } 
    } 
} 

Comando Handler:

public void CreateTeamMember(CreateTeamMemberCommand command) 
{ 
    TeamMemberRoleType role = _lookupService.GetLookupItem<TeamMemberRoleType>(command.RoleTypeID); 

    TeamMember member = TeamMemberFactory.CreateTeamMember(command.TeamMemberID, 
                  role, 
                  command.DateOfBirth, 
                  command.FirstName, 
                  command.Surname); 

    using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork()) 
     _teamMemberRepository.Save(member); 
} 

Il cliente può anche utilizzare il servizio di ricerca per compilare il menu a discesa ecc.:

ILookup<TeamMemberRoleType> roles = _lookupService.GetLookup<TeamMemberRoleType>(); 
+0

Approccio molto interessante David. Grazie per averlo condiviso! Come hai implementato il LookupService? Come servizio di infrastruttura con un metodo Get generico che utilizza direttamente il framework ORM sottostante senza repository? Qualcun altro può commentare questo approccio? Hai fatto qualcosa di simile o hai risolto questo problema in un modo diverso? – Vern

+2

Ho definito ILookupService nella mia infrastruttura, quindi un'implementazione in un progetto separato che viene caricato usando DI (unity). L'implementazione è raw SQL/Sprocs, ma ovviamente è possibile utilizzare facilmente un ORM. Questo servizio può essere utilizzato dal dominio e dai servizi di query per fornire ViewModels all'interfaccia utente. Il motivo per cui utilizzo i generici è perché tutti i 'LookupItems' hanno semplicemente un ID, una descrizione e un elenco di attributi. Ciò significa che posso utilizzare un metodo generico per archiviare i dati per tutte le ricerche/elementi di ricerca utilizzando il loro nome di tipo. OSSIA Non avrei una tabella chiamata 'ProcessType'. –

+0

Vedo. Ho appena provato a implementarlo e sembra una soluzione elegante. Per ora seguirò questo approccio a meno che qualcuno non arrivi e ci dice che hanno una soluzione migliore :-) Lascerò che la domanda rimanga aperta per alcuni giorni per ottenere più opinioni su questo argomento. – Vern

10

Non così semplice. ProcessType è più simile a un oggetto knowledge layer - definisce un determinato processo. Il processo d'altra parte è un'istanza di un processo che è ProcessType. Probabilmente non hai davvero bisogno o vuoi la relazione bidirezionale. Il processo non è probabilmente un figlio logico di un ProcessType. Tipicamente appartengono a qualcos'altro, come un prodotto, o fabbrica o sequenza.

Anche per definizione quando si elimina una radice aggregata si eliminano tutti i membri dell'aggregato. Quando si elimina un processo, dubito seriamente di voler veramente cancellare ProcessType. Se hai eliminato ProcessType, potresti voler eliminare tutti i Processi di quel tipo, ma quella relazione non è già ideale e probabilmente non eliminerai mai gli oggetti definizione non appena avrai un Processo storico definito da ProcessType.

Rimuoverei la raccolta Processi da ProcessType e trovo un genitore più adatto se ne esiste uno. Manterrei il ProcessType come membro di Process poiché probabilmente definisce Process. Gli oggetti Operational Layer (Process) e Knowledge Layer (ProcessType) funzionano raramente come un singolo aggregato, quindi avrei Process essere una radice aggregata o possibilmente trovare una radice aggregata che sia un genitore per il processo. Quindi ProcessType sarebbe una classe esterna. Process.Type è molto probabilmente ridondante poiché hai già Process.ProcessType. Liberati di quello.

Ho un modello simile per l'assistenza sanitaria. Esiste una procedura (livello operativo) e un tipo di procedura (livello della conoscenza). ProcedureType è una classe autonoma. La procedura è un figlio di un terzo oggetto Incontro. L'incontro è la radice aggregata per la procedura. La procedura ha un riferimento a ProcedureType ma è a senso unico. ProcedureType è un oggetto definizione che non contiene una raccolta di procedure.

EDIT (perché i commenti sono così limitati)

Una cosa da tenere a mente attraverso tutto questo. Molti sono puristi del DDD e irremovibili riguardo alle regole. Tuttavia, se leggi attentamente Evans, egli solleva costantemente la possibilità che vengano spesso richiesti compromessi. Fa anche molto per caratterizzare le decisioni di progettazione logiche e attentamente pensate contro cose come le squadre che non capiscono gli obiettivi o aggirano le cose come aggregati per comodità.

Le cose importanti è capire e applicare i concetti in contrasto con le regole.Vedo molti DDD che scaricano un'applicazione in aggregati illogici e confusi ecc. Per nessun'altra ragione che perché viene applicata una regola letterale su repository o attraversamento, che non è l'intento di DDD ma è spesso il prodotto dell'approccio eccessivamente dogmatico di molti prendere.

Ma quali sono i concetti chiave qui:

Aggregati forniscono un mezzo per fare un sistema complesso più gestibile riducendo i comportamenti di molti oggetti in comportamenti più alto livello dei giocatori chiave.

Gli aggregati forniscono un mezzo per garantire che gli oggetti vengano creati in una condizione logica e sempre valida che preservi anche un'unità di lavoro logica tra gli aggiornamenti e le eliminazioni.

Consideriamo l'ultimo punto. In molte applicazioni convenzionali, qualcuno crea un insieme di oggetti che non sono completamente popolati perché devono solo aggiornare o utilizzare alcune proprietà. Il prossimo sviluppatore arriva e ha bisogno anche di questi oggetti, e qualcuno ha già realizzato un set da qualche parte nel quartiere per uno scopo diverso. Ora questo sviluppatore decide di usare solo quelli, ma poi scopre che non hanno tutte le proprietà di cui ha bisogno. Quindi aggiunge un'altra query e compila alcune altre proprietà. Alla fine perché il team non aderisce a OOP perché assume l'atteggiamento comune che l'OOP è "inefficiente e poco pratico per il mondo reale e causa problemi di prestazioni come la creazione di oggetti completi per l'aggiornamento di una singola proprietà". Quello che finiscono con è un'applicazione piena di codice SQL incorporato e oggetti che si materializzano in modo casuale in qualsiasi luogo. Ancor peggio questi oggetti sono dei proxy bastardi non validi. Un processo sembra essere un processo, ma non lo è, è parzialmente popolato in modi diversi in un dato punto a seconda di ciò che era necessario. Si finisce con una palla fangosa di numerose query per riempire parzialmente in modo continuo oggetti a vari gradi e spesso un sacco di schifezze estranee come controlli nulli che non dovrebbero esistere ma sono richiesti perché l'oggetto non è mai veramente valido ecc.

Regole aggregate impedirlo assicurando che gli oggetti vengano creati solo in determinati punti logici e sempre con un set completo di relazioni e condizioni valide. Quindi, ora che capiamo perfettamente a quali regole aggregate ci sono e da cosa ci proteggono, vogliamo anche capire che non vogliamo abusare di queste regole e creare strani aggregati che non riflettono ciò di cui la nostra applicazione si riferisce semplicemente perché queste regole aggregate esistono e devono essere seguite in ogni momento.

Così quando Evans dice di creare Repository solo per aggregati, sta dicendo di creare aggregati in uno stato valido e di mantenerli in questo modo invece di aggirare l'aggregato direttamente per gli oggetti interni. Hai un processo come aggregato di root in modo da creare un repository. ProcessType non fa parte di questo aggregato. cosa fai? Bene, se un oggetto è di per sé ed è un'entità, è un aggregato di 1. Si crea un repository per questo.

Ora il purista arriverà e dirà che non si dovrebbe avere quel repository perché ProcessType è un oggetto valore, non un'entità. Pertanto ProcessType non è affatto un aggregato e pertanto non si crea un repository per questo. Allora cosa fai? Quello che non si fa è calzare il ProcessType in una sorta di modello artificiale per nessun'altra ragione che non è necessario ottenerlo in modo da avere bisogno di un repository ma per avere un repository bisogna avere un'entità come root aggregato. Quello che fai è considerare attentamente i concetti. Se qualcuno ti dice che il repository è sbagliato, ma sai che ne hai bisogno e qualunque cosa possano dire, il tuo sistema di repository è valido e conserva i concetti chiave, mantieni il repository come invece di deformare il tuo modello per soddisfare i dogmi.

Ora, supponendo che io sia corretto su cosa sia ProcessType, come ha notato l'altro commentatore, si tratta in realtà di un oggetto valore. Tu dici che non può essere un oggetto valore. Questo potrebbe essere per diverse ragioni.Forse lo dici perché utilizzi NHibernate ad esempio, ma il modello di NHibernate per l'implementazione di oggetti valore nella stessa tabella di un altro oggetto non funziona. Quindi il tuo ProcessType richiede una colonna e un campo di identità. Spesso a causa delle considerazioni sul database, l'unica implementazione pratica consiste nell'avere oggetti value con id nella propria tabella. O forse lo dici perché ogni processo punta a un singolo ProcessType per riferimento.

Non importa. È un oggetto di valore a causa del concetto. Se si hanno 10 oggetti Process che sono dello stesso ProcessType, si hanno 10 membri e valori Process.ProcessType. Indipendentemente dal fatto che ogni Process.ProcessType punta a un singolo riferimento, o che ognuno abbia una copia, dovrebbero comunque essere tutti per definizione esattamente identici e completamente intercambiabili con gli altri 10. Questo è ciò che lo rende un oggetto Object. La persona che dice "Ha un ID quindi non può essere un valore L'oggetto che hai un'entità" sta facendo un errore dogmatico. Non fare lo stesso errore, se hai bisogno di un campo ID dargli uno, ma non dire "non può essere un oggetto valore" quando in realtà è anche se uno che per altro motivo hai dovuto dare un ID a.

Quindi come si ottiene questo giusto e sbagliato? ProcessType è un oggetto valore, ma per qualche motivo è necessario avere un ID. L'ID di per sé non viola le regole. Hai capito bene con 10 processi che hanno tutti un ProcessType che è esattamente lo stesso. Forse ognuno ha una copia locale di Deeep, forse indicano tutti un oggetto. ma ognuno è identico in entrambi i casi, ad esempio ognuno ha un Id = 2, per esempio. Si ottiene è sbagliato quando si esegue questa operazione: 10 processi ciascuno hanno un ProcessType e questo ProcessType è identico e completamente intercambiabile ECCETTO ora ognuno ha anche il proprio ID univoco. Ora hai 10 istanze della stessa cosa ma variano solo in ID e variano sempre solo in Id. Ora non hai più un oggetto valore, non perché gli hai dato un ID, ma perché hai dato un ID con un'implementazione che riflette la natura di un'entità - ogni istanza è unica e diversa

Ha senso?

+0

Grazie mille - hai assolutamente ragione. Ma secondo Evans, nel suo libro blu, dice che crei repository per gli aggregati. Tuttavia, nel mio scenario, ho bisogno di un repository per la classe standalone (ProcessType), perché devo essere in grado di creare nuovi tipi di processo. Non che sarebbe successo molto spesso, ma è un'opzione. Come lo gestiresti? – Vern

+0

Per creazione, intendo l'oggetto persistente nel database, per il quale utilizzo il modello di repository. Il repository non è responsabile della creazione degli oggetti. – Vern

+0

Quando si utilizza un progetto basato sul livello conoscenza, considerare se la creazione della conoscenza stessa debba o meno essere parte se l'app principale. Dipende. Nel mio caso ci sono milioni di procedure e sintomi. Hai bisogno di uno non nel sistema? C'è un'app per questo. E un medico anziano che deve usarlo. Quindi abbiamo un nuovo paziente che ha carne che mangia bacche di dingle, 1 su 10 casi noti. L'uomo in cima dirà che c'è uno scritto per quello. I dottori pisciano e si lamentano, ma dato il loro modo renderebbero inutilizzabile il loro sistema in un mese. Diverse preoccupazioni ottengono un'app diversa che non fa parte del primario. – Sisyphus

0

Sembra che tu debba ristrutturare il tuo modello. Usa ProcessType come un oggetto Value e Process Agg Root. questo modo ogni processo ha un processType

Public class Process 
{ 
     Public Process() 
     { 

     } 

     public ProcessType { get; } 

} 

per questo u solo bisogno 1 root agg non 2.

+0

ProcessType non può essere un oggetto valore in questo scenario. Non ha senso nel modello. Quindi temo che non sia una soluzione. – Vern

+0

:) controlla le risposte sopra: P –

Problemi correlati