2010-04-01 22 views
6

Ho bisogno di aiuto per trovare la mia radice e il limite aggregati.DDD: Root aggregati

Ho 3 Entità: Piano, PlannedRole e PlannedTraining. Ogni Piano può includere molti PlannedRoles e PlannedTrainings.

Soluzione 1: All'inizio ho pensato Plan è la radice aggregato perché PlannedRole e PlannedTraining non hanno senso fuori del contesto di un piano. Sono sempre all'interno di un piano. Inoltre, abbiamo una regola aziendale che dice che ogni Piano può avere un massimo di 3 PlannedRoles e 5 PlannedTrainings. Così ho pensato nominando il Piano come radice aggregata, posso far valere questo invariante.

Tuttavia, abbiamo una pagina di ricerca in cui l'utente cerca Piani. I risultati mostrano alcune proprietà del Piano stesso (e nessuna delle sue PlannedRoles o PlannedTrainings). Ho pensato che se dovessi caricare l'intero aggregato, ci sarebbe un sacco di spese generali. Ci sono quasi 3000 piani e ognuno può avere alcuni bambini. Caricare tutti questi oggetti insieme e quindi ignorare PlannedRoles e PlannedTrainings nella pagina di ricerca non ha senso per me.

Soluzione 2: Ho appena realizzato che l'utente vuole altre 2 pagine di ricerca dove possono cercare per i ruoli pianificate o previste Training. Questo mi ha fatto capire che stanno cercando di accedere a questi oggetti in modo indipendente e "fuori" dal contesto di Plan. Quindi ho pensato di sbagliarmi sul mio progetto iniziale ed è così che ho trovato questa soluzione. Quindi, ho pensato di avere 3 aggregati qui, 1 per ogni entità.

Questo approccio mi consente di cercare ciascuna Entità in modo indipendente e risolve anche il problema di prestazioni nella soluzione 1. Tuttavia, utilizzando questo approccio non riesco a far rispettare l'invariante che ho menzionato prima.

c'è anche un altro invariante che indica un piano può essere modificato solo se è di un certo status. Pertanto, non dovrei essere in grado di aggiungere alcun PlannedRoles o PlannedTrainings a un Piano che non è in tale stato. Di nuovo, non posso far valere questa invariante con il secondo approccio.

Qualsiasi consiglio sarebbe molto apprezzato.

Cheers, Mosh

risposta

9

ho avuto problemi simili con questo quando si progetta il mio modello e questa domanda che credo potrebbe aiutare, soprattutto per quanto riguarda il primo punto.

DDD - How to implement high-performing repositories for searching.

Quando si tratta di cercare Io non lavoro con il 'modello', repository di ricerca invece mi sono specializzato che restituiscono oggetti 'Sommario' ... vale a dire 'PlanSummary'. Questi non sono altro che oggetti di informazione (potrebbero essere pensati più come rapporti) e non sono usati in senso transazionale - non li definisco nemmeno nella mia libreria di modelli. Creando questi repository e tipi dedicati, posso implementare query di ricerca ad alte prestazioni che possono contenere dati raggruppati (come un conteggio PlannedTraining) senza caricare tutte le associazioni dell'aggregato in memoria. Una volta che l'utente seleziona uno di questi oggetti di riepilogo nell'interfaccia utente, posso quindi utilizzare l'ID per recuperare l'oggetto modello effettivo ed eseguire operazioni transazionali e commit delle modifiche.

Quindi, per la vostra situazione, fornirei questi repository di ricerca specializzati per tutte e tre le entità e quando un utente desidera eseguire un'azione su un'azione, si recupera sempre l'aggregato Plan a cui appartiene.

In questo modo si hanno le ricerche performanti pur mantenendo il vostro unico aggregato con le invarianti richiesti.

Edit - Esempio:

OK, quindi credo che l'attuazione è soggettiva, ma questo è come ho affrontato la situazione nella mia richiesta, utilizzando un aggregato 'membro del team' come un esempio. Esempio scritto in C#. Ho due librerie di classi:

  • Girl
  • Segnalazione

La biblioteca modello contiene la classe di aggregazione, con tutte le invarianti applicate, e la biblioteca di report contiene questo semplice classe:

public class TeamMemberSummary 
{ 
    public string FirstName { get; set; } 

    public string Surname { get; set; } 

    public DateTime DateOfBirth { get; set; } 

    public bool IsAvailable { get; set; } 

    public string MainProductExpertise { get; set; } 

    public int ExperienceRating { get; set; } 
} 

La libreria di report contiene anche la seguente interfaccia:

public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary> 
{ 

} 

Questa è l'interfaccia che il livello dell'applicazione (che nel mio caso risulta essere servizi WCF) consumerà e risolverà l'implementazione tramite il mio contenitore IoC (Unity). L'IReportRepository risiede in una libreria Infrastructure.Interface, come anche una base ReportRepositoryBase. Così ho due diversi tipi di repository nel mio sistema - repository aggregati, e repository di reporting ...

Poi, in un'altra libreria, Repositories.Sql, ho l'attuazione:

public class TeamMemberSummaryRepository : ITeamMemberSummaryRepository 
{ 
    public IList<TeamMemberSummary> FindAll<TCriteria>(TCriteria criteria) where TCriteria : ICriteria 
    { 
     //Write SQL code here 

     return new List<TeamMemberSummary>(); 
    } 

    public void Initialise() 
    { 

    } 
} 

Allora, in il mio livello di applicazione:

public IList<TeamMemberSummary> FindTeamMembers(TeamMemberCriteria criteria) 
    { 
     ITeamMemberSummaryRepository repository 
      = RepositoryFactory.GetRepository<ITeamMemberSummaryRepository>(); 

     return repository.FindAll(criteria); 

    } 

Poi nel client, l'utente può selezionare uno di questi oggetti, ed eseguire un'azione contro uno nel livello di applicazione, ad esempio:

public void ChangeTeamMembersExperienceRating(Guid teamMemberID, int newExperienceRating) 
    { 
     ITeamMemberRepository repository 
      = RepositoryFactory.GetRepository<ITeamMemberRepository>(); 

     using(IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork()) 
     { 
      TeamMember teamMember = repository.GetByID(teamMemberID); 

      teamMember.ChangeExperienceRating(newExperienceRating); 

      repository.Save(teamMember); 
     } 
    } 
+0

Ciao David, Grazie per la tua risposta. Sembra essere una grande idea! Sì, ho letto qualcosa di simile da qualche parte prima ma l'autore non ha approfondito i dettagli di questo. Credo che i risultati della ricerca siano più simili a un rapporto e non vale la pena leggere l'intero aggregato ai fini della segnalazione, poiché i dati sono di sola lettura. Non apporteremo alcuna modifica, quindi nessuna invariante dovrebbe essere applicata, quindi non è richiesto alcun Aggregato! Grande idea! :) Avete qualche esempio di implementazione? O conosci qualche pagina web che ne parli di più? – Mosh

+0

OK, ho aggiunto un esempio alla mia risposta - si spera che vi dia qualche idea! Temo di non conoscere alcun collegamento, dato che sono arrivato io stesso a questa implementazione dopo aver postato la domanda :) P.S. Se questo ti ha aiutato a risolvere il tuo problema, non dimenticare di contrassegnarlo come risposta: D –

+0

Grande implementazione! Sei un genio David! Grazie uomo. – Mosh

4

Il problema reale qui è violazione SRP. La tua parte di input dell'app è in conflitto con l'output.

Attaccare con la prima soluzione (Plan == aggregate root). Promuovere artificialmente le entità (o anche gli oggetti valore) per aggregare le radici distorce l'intero modello di dominio e rovina tutto.


Si potrebbe voler controllare cosiddetta CQRS architettura (query responsabilità di comando segregazione), che si adatterebbe perfettamente per risolvere questo problema particolare. Here's an example app di Mark Nijhof. Ecco la bella lista 'getting-started'.

+0

Da allora ho scoperto CQRS e sì, risolve questo problema esatto. –

3

Questo è il punto di CQRS architetture: segregare Comandi - che modificano il dominio - da Query - che semplicemente dare una vista di stato del dominio, perché requisito di comandi e le richieste sono così diversi.

si può trovare un buon introduzioni su questi blog:

e su molti altri blog (compreso mine)

+0

Non è giusto! Ero primo! : P –

+0

Oops, non avevo notato la seconda parte della risposta: -S dispiace – thinkbeforecoding