Si le modèle de référentiel est excessif pour les ORM modernes (EF, nHibernate), quelle est une meilleure abstraction?

12

J'ai récemment lu de nombreux arguments contre l'utilisation du modèle de référentiel avec de puissants ORM comme Entity Framework, car il intègre également des fonctionnalités de type référentiel, ainsi que des fonctionnalités d'unité de travail.

Un autre argument contre l'utilisation du modèle pour une situation comme les tests unitaires est que le modèle de référentiel est une abstraction qui fuit car les implémentations plus génériques exploitent IQueryable.

Les arguments contre l'utilisation du modèle de référentiel ont du sens pour moi, mais les méthodes alternatives d'abstraction suggérées sont souvent plus confuses et semblent tout aussi exagérées que le problème.

La solution de Jimmy Bogards semble être un mélange de souffler les abstractions, mais aussi d'introduire sa propre architecture. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Un autre exemple de référentiels inutilement .... mais utilisez mon architecture! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

Un autre ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

Je n'ai pas trouvé de remplacement ou d'alternative claire à l'approche de modèle de référentiel «trop complexe» qui n'est pas plus architecturée elle-même.

AnotherDeveloper
la source
4
Qu'est-ce que vous essayez de réaliser concrètement? Les abstractions devraient avoir un but. Si vous écrivez une application CRUD, un ORM est probablement assez abstrait.
JacquesB
@JacquesB J'essaie d'éviter les problèmes d'impédance relationnelle des objets avec un modèle de domaine robuste, mais aussi d'abstraire cela de mes modèles dans une implémentation mvc.
AnotherDeveloper
Reed Cospey a beaucoup de choses positives à dire sur IQuerable ici: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Cela implique que c'est mieux au niveau de la couche transport. En ce qui concerne l'abstraction, j'ai trouvé que l'utilisation du modèle de référentiel générique fonctionnait bien lorsque j'avais besoin d'injecter EntityType mais que je voulais toujours conserver des méthodes communes. D'un autre côté, j'ai moi-même soutenu sur le forum MSDN LINQ que EF est un modèle de référentiel car il est tout en mémoire. Un projet a utilisé de nombreuses clauses Where comme appels de méthode, ce qui a bien fonctionné.
John Peters
Mais un domaine que j'ai étudié mais que j'ai rejeté était celui de l'arbre d'expression, certains l'aiment vraiment, mais ... vous devez l'étudier attentivement avant de le trouver utile.
John Peters
2
nous devrions revenir à appeler SQL à partir des contrôleurs
bobek

Réponses:

12

Je pense que vous confondez les référentiels et les référentiels génériques.

Un référentiel de base interface simplement votre magasin de données et fournit des méthodes pour renvoyer les données

IRepository {
   List<Data> GetDataById(string id);
}

Il ne fuit pas la couche de données dans votre code via un IQueryable ou d'autres moyens de passer dans des requêtes aléatoires et fournit une surface de méthodes testable et injectable bien définie.

Un référentiel générique vous permet de transmettre votre requête un peu comme un ORM

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

Je suis d'accord qu'il ne sert à rien d'utiliser un référentiel générique au-dessus d'un ORM qui est fondamentalement juste un autre référentiel générique.

La réponse est d'utiliser le modèle de référentiel de base pour masquer votre ORM

Ewan
la source
1
Un ORM d'un ORM? J'essayais d'être drôle. Pourquoi avez-vous besoin d'abstraire l'ORM?
johnny
1
même raison que vous abstenez quoi que ce soit. pour éviter de polluer votre code avec des classes propriétaires
Ewan
6

La plupart des arguments que vous mentionnez attribuent à tort aux fonctions de modèle de référentiel qu'il n'a pas.

Sur le plan conceptuel, un référentiel tel que défini à l'origine dans DDD n'est qu'une collection d'objets que vous pouvez rechercher ou ajouter. Le mécanisme de persistance derrière est abstrait, donc en tant que consommateur, vous avez l'illusion qu'il s'agit d'une collection en mémoire.

Une implémentation de référentiel qui a des abstractions qui fuient (exposant IQueryablespar exemple) est une implémentation de référentiel médiocre.

Une implémentation de référentiel qui expose plus que de simples opérations de collecte (par exemple, les fonctionnalités Unit of Work) est une implémentation de référentiel médiocre.

Existe-t-il des alternatives au référentiel pour l'accès aux données? Oui, mais ils ne sont pas liés aux problèmes que vous soulevez dans votre question.

guillaume31
la source
1

Pour moi, les référentiels, combinés avec ORM ou d'autres couches de persistance DB, ont ces inconvénients:

  1. Couvrir les unités de travail. L'UoW doit être codé par le programmeur et peut rarement être implémenté comme une sorte de magie en arrière-plan, où l'utilisateur fait simplement des requêtes et des modifications, sans définir les limites de l'UoW et éventuellement le point de validation. Parfois, l'UoW est abandonné en les réduisant en micro UoW (par exemple des sessions NHibernate) dans chaque méthode d'accès au référentiel.
  2. Couvrir ou, dans le pire des cas, détruire l'ignorance de la persistance: des méthodes comme «Load ()», «Get ()», «Save ()» ou «Update ()» suggèrent des opérations immédiates avec un seul objet, comme si l'envoi d'individus SQL / DML, ou comme si vous travailliez avec des fichiers. En fait, par exemple, les méthodes NHibernate, avec ces noms trompeurs, ne font généralement pas d'accès individuel, mais se mettent en file d'attente pour le chargement paresseux ou le lot d'insertion / mise à jour (Ignorance de persistance). Parfois, les programmeurs se demandent pourquoi ils n'obtiennent pas immédiatement des opérations de base de données et brisent de force l'ignorance de la persistance, tuant ainsi les performances et utilisant des efforts importants pour aggraver le système (bien!).
  3. Croissance incontrôlée. Un référentiel simple peut accumuler de plus en plus de méthodes pour répondre à des besoins spécifiques.

Tel que:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. Objet Danger of God: vous pourriez être tenté de créer une classe divine, couvrant l'ensemble de votre modèle ou couche d'accès aux données. La classe de référentiel contiendrait non seulement des méthodes Car, mais des méthodes pour toutes les entités.

À mon avis, il est préférable d'offrir au moins quelques opportunités de requête, pour éviter le gâchis énorme de nombreuses méthodes à usage unique. Peu importe qu'il s'agisse de LINQ, d'un langage de requête propre, ou même de quelque chose tiré directement de l'ORM (OK, genre de problème de couplage ...).

Erik Hart
la source
4
Où vont toutes ces méthodes si l'on n'utilise pas le modèle de référentiel?
johnny
1

Si le but de l'interface du référentiel est de se moquer de la base de données pour un test unittest (= test isolé), la meilleure abstraction est quelque chose qui est facile à se moquer.

Il est difficile de se moquer d'une interface de référentiel basée sur un résultat IQueryable.

Du point de vue des tests unitaires

IRepository {
   List<Data> GetDataById(string id);
}

peut se moquer facilement

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

ne peut être simulé facilement que si le simulateur ignore le contenu du paramètre de requête.

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

ne peut pas être facilement moqué

k3b
la source
0

Je ne pense pas que le modèle de référentiel soit exagéré, si vous utilisez des fonctions lambda pour interroger. Surtout lorsque vous devez faire abstraction de l'ORM (à mon avis, vous devriez toujours le faire), alors je ne me soucierai pas des détails d'implémentation du référentiel lui-même.

Par exemple:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
dhalsim
la source