2009-08-28 10 views
20

Je suis mise en œuvre du modèle référentiel dans une application et a rencontré deux « problèmes » dans ma compréhension du motif:Pattern Repository meilleures pratiques

  1. Interrogation - J'ai lu des réponses qui ne devrait pas IQueryable être utilisé lors de l'utilisation de référentiels. Cependant, il est évident que vous voudriez que vous ne retourniez pas une liste complète d'objets chaque fois que vous appelez une méthode. Devrait-il être mis en œuvre? Si j'ai une méthode IEnumerable appelée List, quelle est la "meilleure pratique" générale pour un IQueryable? Quels paramètres devraient/devraient pas avoir? Valeurs scalaires - Quelle est la meilleure façon (en utilisant le modèle Repository) de renvoyer une seule valeur scalaire sans avoir à retourner l'ensemble de l'enregistrement? Du point de vue des performances, ne serait-il pas plus efficace de renvoyer une seule valeur scalaire sur une ligne entière?

Répondre

30

Strictement parlant, un référentiel offre une sémantique de collection pour obtenir/placer des objets de domaine. Il fournit une abstraction autour de votre implémentation de la matérialisation (ORM, hand-rolled, mock) afin que les consommateurs des objets du domaine soient découplés de ces détails. En pratique, un référentiel résume généralement l'accès à des entités, c'est-à-dire des objets de domaine avec une identité, et généralement un cycle de vie persistant (dans la version DDD, un référentiel fournit un accès à des racines agrégées).

Une interface minimale pour un dépôt est la suivante:

void Add(T entity); 
void Remove(T entity); 
T GetById(object id); 
IEnumerable<T> Find(Specification spec); 

Bien que vous verrez des différences de nommage et l'ajout de la sémantique Save/saveOrUpdate, ce qui précède est l'idée « pure ». Vous obtenez l'ICollection Ajouter/Supprimer des membres plus quelques trouveurs.Si vous n'utilisez pas IQueryable, vous verrez aussi des méthodes de recherche sur le référentiel comme:

FindCustomersHavingOrders(); 
FindCustomersHavingPremiumStatus(); 

Il y a deux problèmes liés à l'utilisation IQueryable dans ce contexte. Le premier est la possibilité de divulguer au client les détails de la mise en œuvre sous la forme des relations de l'objet du domaine, c'est-à-dire des violations de la loi de Demeter. La seconde est que le référentiel acquiert des responsabilités de recherche qui peuvent ne pas appartenir au référentiel d'objet de domaine proprement dit, par exemple, trouver des projections qui concernent moins l'objet de domaine demandé que les données associées. En outre, l'utilisation de IQueryable 'interrompt' le modèle: Un référentiel avec IQueryable peut ou non fournir un accès aux 'objets de domaine'. IQueryable donne au client beaucoup d'options sur ce qui sera matérialisé lorsque la requête est finalement exécutée. C'est l'idée maîtresse du débat sur l'utilisation de IQueryable. En ce qui concerne les valeurs scalaires, vous ne devez pas utiliser un référentiel pour renvoyer des valeurs scalaires. Si vous avez besoin d'un scalaire, vous obtiendrez généralement cela de l'entité elle-même. Si cela semble inefficace, c'est le cas, mais vous ne le remarquerez peut-être pas, en fonction de vos caractéristiques/exigences de charge. Dans les cas où vous avez besoin d'autres vues d'un objet de domaine, pour des raisons de performances ou parce que vous devez fusionner des données de plusieurs objets de domaine, vous avez deux options.

1) Utilisez le référentiel de l'entité pour trouver les entités spécifiées et le projet/mappage vers une vue aplatie. 2) Créez une interface finder dédiée au retour d'un nouveau type de domaine qui encapsule la vue aplatie dont vous avez besoin. Ce ne serait pas un référentiel, car il n'y aurait pas de sémantique de collection, mais il pourrait utiliser des dépôts existants sous les couvertures. Une chose à considérer si vous utilisez un référentiel «pur» pour accéder aux entités persistantes est que vous compromettez certains des avantages d'un ORM. Dans une implémentation «pure», le client ne peut pas fournir de contexte sur la façon dont l'objet de domaine sera utilisé, donc vous ne pouvez pas dire au référentiel: 'hé, je vais juste changer la propriété customer.Name, donc don ne vous embêtez pas à obtenir ces références pleines d'empressement. D'un autre côté, la question est de savoir si un client devrait savoir à ce sujet. C'est une épée à double tranchant. En ce qui concerne l'utilisation de IQueryable, la plupart des utilisateurs semblent être à l'aise pour «casser» le motif afin d'obtenir les avantages de la composition dynamique des requêtes, en particulier pour les responsabilités du client telles que la pagination/le tri. Dans ce cas, vous pourriez avoir:

Add(T entity); 
Remove(T entity); 
T GetById(object id); 
IQueryable<T> Find(); 

et vous pouvez en finir avec toutes ces méthodes sur mesure du Finder, qui encombrent vraiment le référentiel que vos besoins de requête croître.

+1

Je suis tombé sur le post de jbogard sur ce sujet aujourd'hui: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/09/02/ddd-repository-implementation-patterns.aspx – pfries

+3

+1: ". La plupart des gens semblent être à l'aise avec 'casser' le modèle pour obtenir les avantages de la composition de requête dynamique ... " –

3

En ce qui concerne 1: Pour autant que je peux voir, ce n'est pas le IQuerable lui-même qui est le problème étant de retour d'un référentiel. Le point d'un dépôt est qu'il devrait ressembler à un objet qui contient toutes vos données. Vous pouvez donc demander au référentiel les données. Si vous avez plus d'un objet nécessitant les mêmes données, le travail du référentiel consiste à mettre en cache les données, de sorte que les deux clients de votre référentiel obtiennent les mêmes instances - donc si un client change une propriété, l'autre verra que , car ils pointent vers la même instance. Si le référentiel était en fait le fournisseur Linq lui-même, cela correspondrait parfaitement. Mais la plupart du temps, les utilisateurs laissent simplement passer l'IQuerable du fournisseur de Linq-to-sql, ce qui contourne la responsabilité du référentiel. Donc, le dépôt n'est pas du tout un référentiel, du moins selon ma compréhension et l'utilisation du modèle.

En ce qui concerne 2: Naturellement, il est plus efficace de renvoyer une seule valeur de la base de données que l'enregistrement entier. Mais en utilisant un modèle de référentiel, vous ne retourneriez pas d'enregistrements, vous retourneriez des objets métier. La logique de l'application ne doit donc pas se préoccuper des champs, mais des objets de domaine.

Mais comment est-il plus efficace de renvoyer une seule valeur par rapport à un objet de domaine complet? Vous ne pourrez probablement pas mesurer la différence si votre schéma de base de données est raisonnablement bien défini.

Il est beaucoup plus important d'avoir un code propre et facile à comprendre - au lieu d'optimiser les performances microscopiques à l'avant.

7

En réponse à @lordinateur, je n'aime pas vraiment la façon defacto de spécifier une interface de référentiel. Parce que l'interface dans votre solution nécessite que chaque implémentation de référentiel nécessite au moins un Add, Remove, GetById, etc. Considérez maintenant un scénario dans lequel il n'est pas logique d'enregistrer via une instance particulière d'un référentiel, vous doivent encore implémenter les méthodes restantes avec NotImplementedException ou quelque chose comme ça.

Je préfère partager mes déclarations d'interface du référentiel comme ceci:

interface ICanAdd<T> 
{ 
    T Add(T entity); 
} 

interface ICanRemove<T> 
{ 
    bool Remove(T entity); 
} 

interface ICanGetById<T> 
{ 
    T Get(int id); 
} 

Une mise en œuvre du référentiel particulier pour une entité SomeClass pourrait ressembler ainsi comme suit:

interface ISomeRepository 
    : ICanAdd<SomeClass>, 
     ICanRemove<SomeClass> 
{ 
    SomeClass Add(SomeClass entity); 
    bool Remove(SomeClass entity); 
} 

Prenons un peu de recul et Jetez un oeil à la raison pour laquelle je pense que c'est une meilleure pratique que la mise en œuvre de toutes les méthodes CRUD dans une interface générique.

Certains objets ont différentes exigences que d'autres. Un objet client ne peut pas être supprimé, un objet PurchaseOrder ne peut pas être mis à jour et un objet ShoppingCart ne peut être créé que . Lorsque l'on utilise l'interface générique IRepository, cette provoque évidemment des problèmes dans la mise en œuvre .

ceux qui appliquent l'anti-modèle va souvent mettre en œuvre leur interface complète alors jetteront exceptions pour les méthodes qu'ils ne le font pas soutien. Mis à part en désaccord avec de nombreux principes OO ce casse leur espoir de pouvoir utiliser leur abstraction IRepository efficacement à moins qu'ils ne commencent aussi mettre des méthodes sur pour ou non des objets donnés sont pris en charge et mettre en œuvre les plus .

Une solution commune à ce problème est de passer à plusieurs interfaces granulaires telles que ICanDelete, ICanUpdate, ICanCreate etc etc. Ce tout travaillant autour de nombreux problèmes qui ont pris naissance en termes de OO principes réduit également la quantité de code réutilisation qui est vu que la plupart du temps on ne pourra plus utiliser l'instance de référentiel de béton plus.

Aucun de nous n'aime écrire le même code encore et encore. Cependant, un contrat référentiel comme est une couture architecturale est le mauvais endroit pour élargir le contrat pour le rendre plus générique.

Ces extraits ont été shamelésly à partir de this post où vous pouvez également lire plus de discussion dans les commentaires.

+0

Qu'est-ce qu'un référentiel que vous ne" sauvez "pas? Si vous supprimez la pièce Ajout/Suppression, vous avez simplement une interface Finder. Si tout ce que vous voulez faire est de trouver des choses, vous n'avez pas besoin d'un dépôt mais plutôt d'une implémentation ICustomerFinder.Cela dit, votre dépôt pourrait mettre en œuvre un Finder: Interface ICustomerRepository: IRepository , ICustomerFinder Juste pour clarifier les choses, vous faites à travers l'interface de référentiel pas vraiment « Save », ce serait la responsabilité d'une sorte d'unité de travail la mise en oeuvre. Une telle session est mieux gérée en dehors du référentiel. – pfries

+0

Le point de vue de Greg concernant la composition est bon, c'est-à-dire que vous pouvez implémenter un référentiel d'objet de domaine avec un référentiel générique interne. C'est un peu comme cela fonctionne en pratique lorsqu'on utilise une interface comme ICustomerRepository autour d'un ORM comme NHibernate (qui se substitue à un référentiel générique). Son point principal est de ne pas utiliser des contrats génériques dans les limites de votre application. C'est plutôt raisonnable. Si votre contrat est ICustomerRepository et non IRepository, vous évitez les problèmes qu'il décrit. Je n'aime pas tous les trouveurs même s'ils expriment l'intention. Qu'en est-il des objets de requête? – pfries

+0

froghammr: après toutes mes recherches, je m'installe avec Query Objects. Je développe une application mobile dans MonoDroid/C# et ne possède pas LINQ, Entity ou Hibernate, donc je dois tout écrire de zéro. Malgré cela, les frais généraux d'un Finder/Specification est profond. – samosaris