2010-04-01 12 views
6

J'ai besoin d'aide pour trouver ma racine et ma limite globales.DDD: Racines agrégées

J'ai 3 entités: Plan, PlannedRole et PlannedTraining. Chaque plan peut inclure plusieurs rôles planifiés et planifiés.

Solution 1: J'ai d'abord pensé que Plan était la racine agrégée parce que PlannedRole et PlannedTraining n'ont pas de sens dans le contexte d'un Plan. Ils sont toujours dans un plan. En outre, nous avons une règle de gestion qui stipule que chaque plan peut avoir un maximum de 3 rôles planifiés et de 5 plans de formation. J'ai donc pensé en nommant le Plan comme la racine agrégée, je peux imposer cet invariant.

Cependant, nous avons une page de recherche où l'utilisateur recherche des plans. Les résultats montrent quelques propriétés du Plan lui-même (et aucun de ses PlannedRoles ou PlannedTrainings). Je pensais que si je devais charger tout l'agrégat, cela aurait beaucoup de frais généraux. Il y a près de 3000 plans et chacun peut avoir quelques enfants. Charger tous ces objets ensemble, puis ignorer PlannedRoles et PlannedTrainings dans la page de recherche n'a aucun sens pour moi.

Solution 2: Je viens de réaliser que l'utilisateur veut 2 autres pages de recherche où il peut rechercher des rôles planifiés ou des formations planifiées. Cela m'a fait réaliser qu'ils essaient d'accéder à ces objets indépendamment et «hors» du contexte de Plan. J'ai donc pensé que j'avais tort sur ma conception initiale et c'est ainsi que j'ai trouvé cette solution. Donc, je pensais avoir 3 agrégats ici, 1 pour chaque Entité.

Cette approche me permet de rechercher indépendamment chaque Entité et résout également le problème de performance dans la solution 1. Cependant, en utilisant cette approche, je ne peux pas appliquer l'invariant que j'ai mentionné précédemment.

Il existe également un autre invariant qui indique qu'un Plan ne peut être modifié que s'il a un certain statut. Donc, je ne devrais pas être en mesure d'ajouter des PlannedRoles ou des PlannedTrainings à un plan qui n'est pas dans ce statut. Encore une fois, je ne peux pas imposer cet invariant avec la seconde approche.

Un conseil serait grandement apprécié.

Cheers, Mosh

Répondre

9

J'ai eu des problèmes similaires avec ce lors de la conception de mon modèle et posé cette question que je pense pourrait vous aider, en particulier en ce qui concerne votre premier point.

DDD - How to implement high-performing repositories for searching. En ce qui concerne la recherche, je ne travaille pas avec le 'modèle', mais j'ai des dépôts de recherche spécialisés qui renvoient des objets 'Résumé' ... c'est-à-dire 'PlanSummary'. Ce ne sont rien de plus que des objets d'information (on pourrait parler de reportage) et ne sont pas utilisés dans un sens transactionnel - je ne les définis même pas dans ma bibliothèque de classes de modèles. En créant ces référentiels et types dédiés, je peux implémenter des requêtes de recherche performantes qui peuvent contenir des données groupées (comme un compte PlannedTraining) sans charger toutes les associations de l'agrégat en mémoire. Une fois qu'un utilisateur sélectionne l'un de ces objets récapitulatifs dans l'interface utilisateur, je peux ensuite utiliser l'ID pour extraire l'objet modèle réel et effectuer des opérations transactionnelles et valider les modifications. Donc, pour votre situation, je fournirais ces référentiels de recherche spécialisés pour les trois entités, et lorsqu'un utilisateur souhaite effectuer une action contre un, vous récupérez toujours l'agrégat Plan auquel il appartient. De cette façon, vous avez les recherches performantes tout en conservant votre agrégat unique avec les invariants requis.

Edition - Exemple:

OK, donc je suppose que la mise en œuvre est subjective, mais comment je l'ai traité dans ma demande, en utilisant un agrégat « d'équipe » comme un exemple. Exemple écrit en C#. J'ai deux bibliothèques de classes:

  • Modèle
  • rapports

La bibliothèque de modèle contient la classe globale, avec toutes les invariants appliquées et la bibliothèque de rapports contient cette classe simple:

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 bibliothèque de rapports contient également l'interface suivante:

public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary> 
{ 

} 

Ceci est l'interface que la couche d'application (qui dans mon cas arrive à être des services WCF) va consommer et va résoudre l'implémentation via mon conteneur IoC (Unity). L'IReportRepository réside dans une bibliothèque Infrastructure.Interface, tout comme une base ReportRepositoryBase de base. J'ai donc deux différents types de dépôt dans mon système - dépôts globaux et dépôts de rapports ...

Puis dans une autre bibliothèque, Repositories.Sql, j'ai la mise en œuvre:

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() 
    { 

    } 
} 

Alors, en ma couche application:

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

     return repository.FindAll(criteria); 

    } 

Puis dans le client, l'utilisateur peut sélectionner l'un de ces objets, et effectuer une action contre un dans la couche d'application, par exemple:

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

Salut David, Merci pour ta réponse. Semble être une bonne idée! Oui, j'ai lu quelque chose de similaire quelque part avant mais l'auteur n'a pas creusé dans les détails de ceci. Je crois que les résultats de la recherche ressemblent plus à un rapport et qu'il ne vaut pas la peine de lire l'ensemble de l'agrégat à des fins de rapport, puisque les données sont en lecture seule. Nous ne ferons aucun changement, donc aucun invariant ne devrait être appliqué, donc aucun agrégat n'est requis! Bonne idée! :) Avez-vous des exemples d'implémentation? Ou connaissez-vous des pages Web qui en parlent plus? – Mosh

+0

OK, j'ai ajouté un exemple à ma réponse - j'espère que cela vous donne quelques idées! J'ai peur de ne pas connaître de liens, car j'ai moi-même trouvé cette implémentation après avoir posté cette question :) P.S. Si cela vous a aidé à résoudre votre problème, n'oubliez pas de marquer ceci comme la réponse: D –

+0

Grande implémentation! Tu es un génie David! Merci mec. – Mosh

4

Le vrai problème ici est la violation de la SRP. Votre partie entrée de l'application entre en conflit avec la sortie.

Collez avec la première solution (Plan == racine d'agrégat). Promouvoir artificiellement des entités (ou même valoriser des objets) pour agréger des racines déforme tout le modèle de domaine et ruine tout.


Vous pouvez vérifier que l'on appelle CQRS (responsabilité commande requête ségrégation) l'architecture qui cadrerait parfaitement pour résoudre ce problème particulier. Here's an example app par Mark Nijhof. Voici la liste 'getting-started'.

+0

J'ai depuis découvert CQRS et oui, il résout exactement ce problème. –

3

Ceci est le point entier de CQRS architectures: commandes ségréger - qui modifient le domaine - des requêtes - qui donnent simplement une vue de l'état de domaine, parce que la condition pour les commandes et les requêtes sont si différents.

vous pouvez trouver une bonne introduction sur ces blogs:

et sur beaucoup d'autres blogs (y compris mine)

+0

Ce n'est pas juste! J'étais en premier! : P –

+0

Oups, je n'avais pas remarqué la deuxième partie de votre réponse: -S désolé – thinkbeforecoding