Comment utiliser correctement le modèle de référentiel?

86

Je me demande comment dois-je regrouper mes référentiels? Comme dans les exemples que j'ai vus sur le mvc asp.net et dans mes livres, ils utilisent essentiellement un référentiel par table de base de données. Mais cela semble être beaucoup de référentiels qui vous obligent à appeler de nombreux référentiels plus tard pour des moqueries et autres.

Donc je suppose que je devrais les regrouper. Cependant, je ne sais pas comment les regrouper.

En ce moment, j'ai créé un référentiel d'enregistrement pour gérer tout mon enregistrement. Cependant, il y a comme 4 tables que je dois mettre à jour et avant j'avais 3 référentiels pour le faire.

Par exemple, l'une des tables est une table de licence. Quand ils s'inscrivent, je regarde leur clé et la vérifie pour voir si elle existe dans la base de données. Maintenant, que se passe-t-il si je dois vérifier cette clé de licence ou quelque chose d'autre de cette table à un autre endroit que l'enregistrement?

Un endroit pourrait être la connexion (vérifiez si la clé n'est pas expirée).

Alors, que ferais-je dans cette situation? Réécrire à nouveau le code (interrompre DRY)? Essayez de regrouper ces 2 dépôts et espérez qu'aucune des méthodes ne sera nécessaire à un autre moment (comme peut-être que je pourrais avoir une méthode qui vérifie si userName est utilisé - peut-être que j'aurai besoin de cela ailleurs).

De plus, si je les fusionne, j'aurais soit besoin de 2 couches de service allant dans le même référentiel car je pense qu'avoir toute la logique pour 2 parties différentes d'un site serait long et je devrais avoir des noms comme ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () et etc.

Ou appeler le référentiel de toute façon et avoir juste un nom bizarre?

Il semble juste difficile de créer un référentiel qui a un nom assez général pour que vous puissiez l'utiliser pour de nombreux endroits de votre application et qui ait toujours du sens et je ne pense pas qu'appeler un autre référentiel dans un référentiel serait une bonne pratique?

chobo2
la source
11
+1. Excellente question.
griegs
Merci, ça me dérange depuis un certain temps maintenant. Puisque je trouve que ceux que j'ai vus dans le livre sont trop simples, ils ne vous montrent pas quoi faire dans ces situations.
chobo2
Je fais essentiellement la même chose que vous et ouais si j'ai une classe linq2sql qui est utilisée dans plus d'un référentiel et que j'ai besoin de changer la structure de la table, je casse DRY. Moins qu'idéal. Je le planifie maintenant un peu mieux pour ne pas avoir besoin d'utiliser une classe linq2sql plus d'une fois, ce qui, je suppose, est une bonne séparation des préoccupations, mais je prévois un jour où cela va être un vrai problème pour moi.
griegs
J'ai posé une question similaire (mais pas identique) ici: stackoverflow.com/questions/910156
...
Oui, mais il semble difficile de le planifier pour toutes les situations. Comme je l'ai dit, je peux peut-être fusionner la connexion et l'enregistrement dans une authentification et avoir 2 couches séparées. Cela résoudra probablement mon problème. Mais que se passe-t-il si, par exemple, la page de profil de mon site, je veux leur montrer leur clé pour une raison quelconque (peut-être que je vais le laisser changer ou quelque chose). Maintenant, que dois-je faire pour casser DRY et écrire la même chose? Ou essayez de créer un référentiel qui puisse d'une manière ou d'une autre intégrer ces 3 tables avec un bon nom.
chobo2

Réponses:

41

Une chose que j'ai mal faite en jouant avec le modèle de référentiel - tout comme vous, je pensais que ce tableau se rapportait au référentiel 1: 1. Lorsque nous appliquons certaines règles de Domain Driven Design, le problème de regroupement des référentiels disparaît souvent.

Le référentiel doit être par racine agrégée et non par table. Cela signifie - si l'entité ne doit pas vivre seule (c'est-à-dire si vous avez un Registrantqui participe en particulier Registration) - c'est juste une entité, elle n'a pas besoin d'un référentiel, elle doit être mise à jour / créée / récupérée via le référentiel de la racine agrégée. fait parti.

Bien sûr - dans de nombreux cas, cette technique de réduction du nombre de référentiels (en fait - c'est plus une technique pour structurer votre modèle de domaine) ne peut pas être appliquée car chaque entité est censée être une racine agrégée (cela dépend fortement de votre domaine, Je ne peux fournir que des suppositions aveugles). Dans votre exemple - Licensesemble être une racine agrégée car vous devez être en mesure de les vérifier sans contexte d' Registrationentité.

Mais cela ne nous limite pas aux référentiels en cascade (le Registrationréférentiel est autorisé à référencer le Licenseréférentiel si nécessaire). Cela ne nous limite pas à référencer le Licenseréférentiel (préférable - via IoC) directement à partir de l' Registrationobjet.

Essayez simplement de ne pas conduire votre conception à des complications liées aux technologies ou à un malentendu. Regrouper les référentiels ServiceXsimplement parce que vous ne voulez pas construire 2 référentiels n'est pas une bonne idée.

Il serait préférable de lui donner un nom propre - RegistrationServicec'est-à - dire

Mais les services doivent être évités en général - ils sont souvent la cause qui conduit à un modèle de domaine anémique .

EDIT:
commencez à utiliser IoC. Cela soulage vraiment la douleur de l'injection de dépendances.
Au lieu d'écrire:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

vous pourrez écrire:

var registrationService = IoC.Resolve<IRegistrationService>();

Ps serait préférable d'utiliser ce qu'on appelle le localisateur de services communs, mais ce n'est qu'un exemple.

Arnis Lapsa
la source
17
Hahaha ... Je donne des conseils pour utiliser le localisateur de services. Toujours de la joie de voir à quel point j'ai été stupide dans le passé.
Arnis Lapsa
1
@Arnis Il semble que vos opinions aient changé - je suis intéressé de savoir comment répondriez-vous différemment à cette question maintenant?
ngm le
10
@ngm a beaucoup changé depuis que j'ai répondu à cela. Je suis toujours d'accord pour dire que la racine agrégée devrait tracer des limites transactionnelles (être sauvegardées dans son ensemble), mais je suis beaucoup moins optimiste quant à l'abstraction de la persistance à l'aide d'un modèle de référentiel. Dernièrement, j'utilise juste ORM directement, car des choses comme la gestion du chargement impatient / paresseux sont trop gênantes. Il est beaucoup plus avantageux de se concentrer sur le développement d'un modèle de domaine riche au lieu de se concentrer sur la persistance abstraite.
Arnis Lapsa le
2
@Developer non, pas exactement. ils devraient toujours être ignorants de la persévérance. vous récupérez la racine agrégée de l'extérieur et appelez la méthode qui fait le travail. mon modèle de domaine n'a aucune référence, juste celles du framework .net standard. pour y parvenir, vous devez avoir un modèle de domaine riche et des outils suffisamment intelligents (NHibernate fait l'affaire).
Arnis Lapsa
1
Au contraire. La récupération de plus d'un ggregate signifie souvent que vous pouvez utiliser le PK ou les index. Beaucoup plus rapide que de tirer parti des relations générées par EF. Plus les agrégats racine sont petits, plus il est facile d'obtenir des performances sur eux.
jgauffin
5

Une chose que j'ai commencé à faire pour résoudre ce problème est de développer des services qui enveloppent N référentiels. J'espère que vos frameworks DI ou IoC pourront vous faciliter la tâche.

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

Cela a-t-il du sens? De plus, je comprends que parler de services dans ce manoir peut ou non être conforme aux principes DDD, je le fais simplement parce que cela semble fonctionner.

neouser99
la source
1
Non, cela n'a pas beaucoup de sens. Je n'utilise pas de frameworks DI ou IoC pour le moment car j'en ai assez dans mon assiette tel quel.
chobo2
1
Si vous pouvez instancier vos dépôts avec juste un nouveau (), vous pouvez essayer ceci ... public ServiceImpl (): this (new Repo1, new Repo2 ...) {} comme constructeur supplémentaire dans le service.
neouser99
Injection de dépendance? Je le fais déjà mais je ne sais toujours pas quel est votre code et ce qu'il résout.
chobo2
Je vais spécifiquement après la fusion des référentiels. Quel code n'a pas de sens? Si vous utilisez DI, alors le code de la réponse fonctionnera dans le sens où votre framework DI injectera ces services IRepo, dans le code de commentaire, il s'agit simplement d'un petit travail pour faire DI (fondamentalement votre constructeur sans paramètre `` injecte '' ces dépendances dans votre ServiceImpl).
neouser99
J'utilise déjà DI donc je peux mieux tester les unités. Mon problème est que si vous créez vos couches de service et vos dépôts avec des noms détaillés. Ensuite, si jamais vous avez besoin de les utiliser ailleurs, cela semblera étrange, comme le RegistrationService Layer qui appelle le RegRepo dans une autre classe, par exemple le ProfileClass. Je ne vois donc pas de votre part ce que vous faites pleinement. Comme si vous commencez à avoir trop de Repos dans la même couche de service, vous aurez une logique commerciale et une logique de validation tellement différentes. Puisque dans la couche de service, vous mettez généralement en logique de validation. J'en ai besoin de plus ...
chobo2
2

Ce que je fais, c'est que j'ai une classe de base abstraite définie comme suit:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

Vous pouvez ensuite dériver votre référentiel de la classe de base abstraite et étendre votre référentiel unique tant que les arguments génériques diffèrent par exemple;

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

etc....

J'ai besoin des référentiels séparés car nous avons des restrictions sur certains de nos référentiels et cela nous donne une flexibilité maximale. J'espère que cela t'aides.

Michael Mann
la source
Vous essayez donc de créer un référentiel générique pour gérer tout cela?
chobo2
Donc, ce qui se passe réellement dans la méthode de mise à jour. Comme vous l'avez fait cette mise à jour V et elle passe dans une entité T àUpdate mais il n'y a pas de code qui la met à jour ou y a-t-il?
chobo2
Oui .. Il y aura du code dans la méthode de mise à jour car vous écrirez une classe qui descend du référentiel générique. Le code d'implémentation peut être Linq2SQL ou ADO.NET ou tout ce que vous avez choisi comme technologie d'implémentation d'accès aux données
Michael Mann
2

J'ai ceci comme classe de référentiel et oui, j'étends dans le référentiel table / zone mais je dois quand même parfois casser DRY.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

ÉDITER

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

J'ai également un datacontext qui a été créé à l'aide de la classe Linq2SQL.dbml.

Alors maintenant, j'ai un référentiel standard implémentant des appels standard comme All et Find et dans mon AdminRepository j'ai des appels spécifiques.

Ne répond pas à la question de DRY bien que je ne pense pas.

griegs
la source
À quoi sert "Repository"? et comme CreateInstance? Je ne sais pas ce que tout ce que vous avez fait.
chobo2
C'est générique comme n'importe quoi. Fondamentalement, vous devez avoir un référentiel spécifique pour votre (zone). Consultez la modification ci-dessus pour mon AdminRepository.
griegs
1

Voici un exemple d'implémentation générique de référentiel utilisant FluentNHibernate. Il est capable de conserver n'importe quelle classe pour laquelle vous avez écrit un mappeur. Il est même capable de générer votre base de données à partir des classes de mappeur.

James Jones
la source
1

Le modèle de référentiel est un mauvais modèle de conception. Je travaille avec de nombreux anciens projets .Net et ce modèle provoque généralement des erreurs «Transactions distribuées», «Restauration partielle» et «Pool de connexions épuisé» qui pourraient être évitées. Le problème est que le modèle tente de gérer les connexions et les transactions en interne, mais celles-ci doivent être gérées au niveau de la couche contrôleur. EntityFramework fait également déjà abstraction d'une grande partie de la logique. Je suggérerais d'utiliser le modèle de service à la place pour réutiliser le code partagé.

ColacX
la source
0

Je vous suggère de regarder Sharp Architecture . Ils suggèrent d'utiliser un référentiel par entité. Je l'utilise actuellement dans mon projet et je suis très satisfait des résultats.

Sournois
la source
Je donnerais un +1 ici, mais il a indiqué que les conteneurs DI ou IoC ne sont pas tout à fait une option (étant donné que ce ne sont pas les seuls avantages de Sharp Arch). Je suppose qu'il y a beaucoup de code existant sur lequel il travaille.
neouser99
Qu'est-ce qui est considéré comme une entité? Est-ce la base de données entière? ou est-ce une table de base de données? Les conteneurs DI ou IoC ne sont pas une option pour le moment car je ne veux tout simplement pas apprendre cela sur les 10 autres choses que j'apprends en même temps. ils sont quelque chose que je vais examiner dans ma prochaine révision de mon site ou mon prochain projet. Même si je ne suis pas sûr que ce soit celui-ci, jetez un coup d'œil sur le site et il semble que vous souhaitiez utiliser nhirbrate et j'utilise pour linq to sql en ce moment.
chobo2