web-dev-qa-db-fra.com

Réduire les référentiels pour agréger les racines

J'ai actuellement un référentiel pour à peu près toutes les tables de la base de données et j'aimerais m'aligner davantage sur DDD en les réduisant à des racines agrégées uniquement.

Supposons que j’ai les tables suivantes, User et Phone. Chaque utilisateur peut avoir un ou plusieurs téléphones. Sans la notion de racine globale, je pourrais faire quelque chose comme ceci:

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

Le concept de racines agrégées est plus facile à comprendre sur papier que dans la pratique. Je n'aurai jamais de numéros de téléphone qui n'appartiennent pas à un utilisateur. Il serait donc logique de supprimer PhoneRepository et d'intégrer des méthodes liées au téléphone dans UserRepository. En supposant que la réponse soit oui, je vais réécrire l'exemple de code précédent.

Suis-je autorisé à utiliser une méthode sur le référentiel d'utilisateurs qui renvoie des numéros de téléphone? Ou devrait-il toujours renvoyer une référence à un utilisateur, puis parcourir la relation via cet utilisateur pour obtenir les numéros de téléphone:

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

Quel que soit le mode d’acquisition des téléphones, en supposant que j’en ai modifié un, comment puis-je les mettre à jour? Ma compréhension limitée est que les objets situés sous la racine doivent être mis à jour via la racine, ce qui me permettrait de choisir le choix n ° 1 ci-dessous. Bien que cela fonctionne parfaitement avec Entity Framework, cela semble extrêmement non descriptif, car la lecture du code ne donne aucune idée de ce que je mets à jour, même si Entity Framework garde un œil sur les objets modifiés dans le graphique.

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

Enfin, en supposant que j'ai plusieurs tables de recherche qui ne sont liées à rien, telles que CountryCodes, ColorsCodes, SomethingElseCodes. Je pourrais les utiliser pour remplir des listes déroulantes ou pour une autre raison. S'agit-il de référentiels autonomes? Peuvent-ils être combinés dans une sorte de groupe/référentiel logique tel que CodesRepository? Ou est-ce contre les meilleures pratiques?.

82
e36M3

Vous êtes autorisé à utiliser n'importe quelle méthode de votre choix dans votre référentiel :) Dans les deux cas que vous mentionnez, il est logique de renvoyer l'utilisateur avec la liste de contacts remplie. Normalement, l'objet utilisateur ne contient pas toutes les informations secondaires (par exemple, toutes les adresses, tous les numéros de téléphone) et nous pouvons utiliser différentes méthodes pour obtenir l'objet utilisateur rempli d'informations différentes. Ceci est appelé chargement paresseux.

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

Pour la mise à jour, dans ce cas, l'utilisateur est mis à jour, pas le numéro de téléphone lui-même. Le modèle de stockage peut stocker les téléphones dans différentes tables et de cette façon, vous pouvez penser que seuls les téléphones sont mis à jour, mais ce n'est pas le cas si vous pensez du point de vue de DDD. En ce qui concerne la lisibilité, alors que la ligne

UserRepository.Update(user)

seul ne communique pas ce qui est mis à jour, le code ci-dessus indiquerait clairement ce qui est mis à jour. En outre, cela ferait très probablement partie d'un appel de méthode front-end pouvant indiquer ce qui est mis à jour.

Pour les tables de recherche, et même autrement, il est utile de disposer de GenericRepository et de l'utiliser. Le référentiel personnalisé peut hériter de GenericRepository.

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Recherchez Generic Entity Repository Entity Framework et vous apprécierez de nombreuses implémentations de Nice. Utilisez-en un ou écrivez le vôtre.

11
amit_g

Votre exemple sur le référentiel de la racine d'agrégat est parfaitement correct, c'est-à-dire que toute entité qui ne peut raisonnablement exister sans dépendance vis-à-vis d'une autre ne devrait pas avoir son propre référentiel (dans votre cas, Phone). Sans cette considération, vous pouvez rapidement vous retrouver avec une explosion de référentiels dans un mappage 1-1 de tables de base de données.

Vous devriez envisager d'utiliser le modèle d'unité de travail pour les modifications de données plutôt que les référentiels eux-mêmes, car je pense que cela vous cause une certaine confusion quant à l'intention des modifications persistantes de la base de données. Dans une solution EF, l’Unité de travail est essentiellement un encapsuleur d’interface autour de votre contexte EF.

En ce qui concerne votre référentiel pour les données de recherche, nous créons simplement un ReferenceDataRepository qui devient responsable des données qui n'appartiennent pas spécifiquement à une entité de domaine (pays, couleurs, etc.).

9
Darren Lewis

Si le téléphone n'a aucun sens utilisateur, il s'agit d'une entité (si vous vous souciez de son identité) ou d'un objet de valeur et doit toujours être modifié par l'utilisateur et récupéré/mis à jour ensemble.

Pensez aux racines agrégées en tant que définisseurs de contexte - elles dessinent des contextes locaux mais se situent elles-mêmes dans un contexte global (votre application).

Si vous suivez une conception pilotée par un domaine, les référentiels sont censés contenir 1: 1 par racine agrégée.
Pas d'excuses.

Je parie que ce sont des problèmes auxquels vous faites face:

  • difficultés techniques - inadéquation impédance relation d'objet. Vous avez du mal à conserver les graphiques d'objet entier persistants avec facilité et le type de structure d'entité ne parvient pas à vous aider.
  • modèle de domaine est centré sur les données (par opposition à centré sur le comportement). à cause de cela - vous perdez la connaissance de la hiérarchie des objets (contextes précédemment mentionnés) et, comme par magie, tout devient une racine agrégée.

Je ne suis pas sûr de savoir comment résoudre le premier problème, mais j'ai remarqué que la résolution du deuxième problème était suffisante. Pour comprendre ce que je veux dire par comportement centré, donnez cet article à essayer.

P.s. Réduire le référentiel pour agréger la racine n'a aucun sens.
P.p.s. Évitez "CodeRepositories". Cela conduit à des données centrées -> code procédural.
P.p.p.s Évitez l’unité de travail. Les racines agrégées doivent définir les limites des transactions.

5
Arnis Lapsa

C'est une vieille question, mais je pense que ça vaut la peine de poster une solution simple.

  1. Le contexte EF vous donne déjà à la fois l'unité de travail (suit les modifications) et les référentiels (référence en mémoire pour les éléments de la base de données). Une abstraction supplémentaire n'est pas obligatoire.
  2. Supprimez le DBSet de votre classe de contexte, car Phone n’est pas une racine agrégée.
  3. Utilisez plutôt la propriété de navigation "Phones" sur User.

static void updateNumber (int userId, chaîne oldNumber, chaîne newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }
3
Chalky

Si une entité Phone n’a de sens qu’avec un utilisateur racine agrégé, je pense aussi qu’il est logique que l’opération d’ajout d’un nouvel enregistrement Phone relève de la responsabilité de l’objet Domaine de l’utilisateur via une méthode spécifique (comportement DDD), ce qui pourrait parfaitement logique pour plusieurs raisons, la première raison est de vérifier l’existence de l’objet Utilisateur puisque l’entité Phone en dépend et de conserver un verrou de transaction tout en effectuant davantage de vérifications de validation afin de s’assurer qu’aucun autre processus n’a supprimé l’agrégat racine avant. nous avons fini de valider l'opération. Dans d'autres cas, avec d'autres types d'agrégats racine, vous voudrez peut-être agréger ou calculer une valeur et la conserver dans les propriétés de colonne de l'agrégat racine pour un traitement plus efficace par d'autres opérations ultérieurement. Notez que bien que je suggère à l’objet Domaine de l’utilisateur d’avoir une méthode qui ajoute le téléphone, cela ne veut pas dire qu’il devrait connaître l’existence de la base de données ou de l’EF; une des fonctionnalités intéressantes de EM et Hibernate est qu’ils peuvent suivre les modifications apportées à une entité. les classes de manière transparente, ce qui signifie également l'ajout de nouvelles entités associées par leurs propriétés de collection de navigation. 

De même, si vous souhaitez utiliser des méthodes permettant d'extraire tous les téléphones, quels que soient leurs utilisateurs, vous pouvez toujours utiliser le référentiel d'utilisateurs. Une seule méthode renvoie tous les utilisateurs au format IQueryable. Vous pouvez ensuite les mapper pour obtenir tous les téléphones des utilisateurs et effectuer une analyse plus précise. requête avec ça. Donc, vous n'avez même pas besoin d'un PhoneRepository dans ce cas. De plus, je préférerais utiliser une méthode avec des extensions pour IQueryable que je peux utiliser n'importe où, pas seulement à partir d'une classe Repository, si je voulais résumer des requêtes derrière des méthodes.

Juste un inconvénient pour pouvoir supprimer des entités de téléphone en utilisant uniquement l'objet de domaine et non un référentiel de téléphone, vous devez vous assurer que l'ID utilisateur fait partie de la clé primaire du téléphone ou, en d'autres termes, que la clé primaire d'un enregistrement de téléphone est une clé composite. composé de UserId et d'une autre propriété (je suggère une identité générée automatiquement) dans l'entité Phone. Cela a un sens intuitif puisque l’enregistrement téléphonique appartient à l’enregistrement utilisateur et que sa suppression de la collection de navigation de l’utilisateur équivaut à sa suppression complète de la base de données.

0
Kaveh Hadjari