Если шаблон репозитория избыточен для современных ORM (EF, nHibernate), что является лучшей абстракцией?

12

Недавно я прочитал множество аргументов против использования шаблона репозитория с мощной ORM-подобной Entity Framework, поскольку она включает в себя функциональность, подобную репозиторию, а также функциональность Unit of Work.

Еще один аргумент против использования шаблона для ситуации, такой как модульное тестирование, заключается в том, что шаблон репозитория является утечкой абстракции, поскольку более общие реализации используют IQueryable.

Аргументы против использования шаблона репозитория имеют смысл для меня, но предложенные альтернативные методы абстракций часто более запутанны и кажутся такими же излишними, как и проблема.

Решение Джимми Богардса, похоже, представляет собой смесь издувания абстракций, а также представления его собственной архитектуры. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Еще один пример того, что репозитории были излишни .... но используйте мою архитектуру! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

Другой ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

Я не нашел четкой замены или альтернативы «чрезмерно сложному» подходу к шаблону репозитория, который сам по себе более не спроектирован.

AnotherDeveloper
источник
4
Что конкретно вы пытаетесь достичь? У абстракций должна быть цель. Если вы пишете приложение CRUD, ORM, вероятно, достаточно абстрактно.
JacquesB
@JacquesB Я пытаюсь избежать проблем объектного реляционного импеданса с надежной моделью предметной области, но также абстрагируюсь от моих моделей представления в реализации mvc.
AnotherDeveloper
У Рида Коспи есть много положительных моментов, которые можно сказать об IQuerable здесь: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Это означает, что он лучше на транспортном уровне. Что касается абстракции, я обнаружил, что использование шаблона универсального репозитория хорошо работает, когда мне нужно было внедрить EntityType, но все же хотелось поддерживать общие методы. С другой стороны, я сам на форуме MSDN LINQ утверждал, что EF - это шаблон репозитория, потому что он находится в памяти. Один проект использовал многочисленные предложения Where как вызовы методов, которые работали хорошо.
Джон Питерс
Но одной областью, которую я изучил, но отверг, был бизнес Expression Tree, некоторым это действительно нравится, но ... вам нужно тщательно изучить его, прежде чем он окажется полезным.
Джон Питерс
2
мы должны вернуться к вызову SQL из контроллеров
bobek

Ответы:

12

Я думаю, что вы объединяете репозитории и общие репозитории.

Базовый репозиторий просто связывает ваше хранилище данных и предоставляет методы для возврата данных

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

Он не пропускает слой данных в ваш код через IQueryable или другие способы передачи случайных запросов и обеспечивает четко определенную тестируемую и инъецируемую поверхность методов.

Универсальный репозиторий позволяет передавать ваш запрос так же, как ORM

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

Я согласен, что нет смысла использовать универсальный репозиторий поверх ORM, который по сути является просто еще одним универсальным репозиторием.

Ответ заключается в том, чтобы использовать базовый шаблон репозитория, чтобы скрыть свой ORM

Ewan
источник
1
ОРМ ОРМ? Я пытался быть смешным. Зачем вам нужно абстрагировать ОРМ?
джонни
1
По той же причине, по которой вы что-то абстрагируете. чтобы избежать загрязнения вашего кода
Ewan
6

Большинство аргументов, которые вы упоминаете, ошибочно приписывают тем возможностям шаблона репозитория, которых у него нет.

Концептуально репозиторий, изначально определенный в DDD, - это просто набор объектов, которые вы можете искать или добавлять. Механизм постоянства, стоящий за ним, абстрагирован, поэтому у потребителя возникает иллюзия, что это коллекция в памяти.

Реализация репозитория, имеющая неплотные абстракции ( IQueryablesнапример, разоблачающие ), является плохой реализацией репозитория.

Реализация репозитория, которая предоставляет больше, чем просто операции сбора (например, функции Unit of Work), является плохой реализацией репозитория.

Существуют ли альтернативы хранилищу для доступа к данным? Да, но они не связаны с вопросами, которые вы затрагиваете в своем вопросе.

guillaume31
источник
1

Для меня репозитории в сочетании с ORM или другими уровнями персистентности БД имеют следующие недостатки:

  1. Сокрытие единиц труда. UoW должен быть закодирован программистом и редко может быть реализован как своего рода волшебство в фоновом режиме, когда пользователь просто делает запросы и модификации, не определяя границы UoW и, возможно, точку фиксации. Иногда UoW отбрасывают, сокращая их до микро UoW (например, сеансов NHibernate) в каждом методе доступа к репозиторию.
  2. Сокрытие или, в худшем случае, уничтожение постоянного невежества: такие методы, как «Load ()», «Get ()», «Save ()» или «Update ()», предлагают немедленные операции с одним объектом, как если бы отправлялись отдельные SQL / DML, или как будто работа с файлами. Фактически, например, методы NHibernate с такими вводящими в заблуждение именами обычно не создают индивидуальный доступ, а ставят в очередь отложенную загрузку или пакет вставки / обновления (постоянное игнорирование). Иногда программисты задаются вопросом, почему они не получают немедленных операций с БД и принудительно разрушают постоянное невежество, тем самым снижая производительность и прикладывая значительные усилия, чтобы реально сделать систему (намного!) Хуже.
  3. Неконтролируемый рост. Простой репозиторий может накапливать все больше и больше методов для удовлетворения конкретных потребностей.

Такие как:

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. Объект «Опасность Бога»: у вас может возникнуть соблазн создать один класс Бога, охватывающий всю вашу модель или слой доступа к данным. Класс репозитория будет содержать не только методы Car, но и методы для всех сущностей.

На мой взгляд, лучше предложить хотя бы несколько вариантов запросов, чтобы избежать огромного беспорядка многих одноцелевых методов. Неважно, является ли это LINQ, собственным языком запросов или даже чем-то, взятым непосредственно из ORM (хорошо, проблема связывания ...).

Эрик Харт
источник
4
Куда идут все эти методы, если не используется Шаблон репозитория?
джонни
@johnny здесь вы идете: softwareengineering.stackexchange.com/a/220126/100617
лексема
1

Если целью интерфейса Repository является макетирование базы данных для юнит- теста (= изолированного теста), то лучшая абстракция - это то, что легко смоделировать.

Трудно смоделировать интерфейс репозитория, основанный на результате IQueryable.

С точки зрения юнит-тестирования

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

может быть легко издеваться

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

может быть легко смоделировано, только если mock игнорирует содержимое параметра запроса.

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

не может быть легко издеваться

k3b
источник
0

Я не думаю, что шаблон репозитория является излишним, если вы используете лямбда-функции для запросов. Особенно, когда вам нужно абстрагировать ORM (по моему мнению, вы всегда должны это делать), тогда я не буду заботиться о деталях реализации самого репозитория.

Например:

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
источник