Jeśli wzorzec repozytorium jest nadmierny dla współczesnych ORM (EF, nHibernate), co jest lepszą abstrakcją?

12

Niedawno przeczytałem wiele argumentów przeciwko używaniu wzorca repozytorium z potężnymi mechanizmami ORM, takimi jak Entity Framework, ponieważ zawiera on funkcje podobne do repozytorium, a także funkcjonalność jednostki pracy.

Kolejnym argumentem przeciwko używaniu wzorca do sytuacji takiej jak testowanie jednostkowe jest to, że wzorzec repozytorium jest nieszczelną abstrakcją, ponieważ bardziej ogólne implementacje wykorzystują IQueryable.

Argumenty przeciwko użyciu wzorca repozytorium mają dla mnie sens, ale sugerowane alternatywne metody abstrakcji są często bardziej mylące i wydają się równie przesadne jak problem.

Rozwiązanie Jimmy'ego Bogardsa wydaje się być mieszanką zdmuchiwania abstrakcji, ale także wprowadzania własnej architektury. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Kolejny przykład niepotrzebnych repozytoriów .... ale używaj mojej architektury! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

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

Nie znalazłem wyraźnego zamiennika lub alternatywy dla „zbyt złożonego” podejścia do wzorców repozytoriów, które samo w sobie nie jest bardziej architektoniczne.

AnotherDeveloper
źródło
4
Co konkretnie próbujesz osiągnąć? Abstrakcje powinny mieć cel. Jeśli piszesz aplikację CRUD, ORM jest prawdopodobnie wystarczająco abstrakcyjne.
JacquesB,
@JacquesB Staram się unikać problemów związanych z impedancją relacyjną obiektu za pomocą solidnego modelu domeny, ale także abstraktuję to z dala od moich modeli w implementacji mvc.
AnotherDeveloper
Reed Cospey ma wiele pozytywnych rzeczy do powiedzenia na temat IQuerable tutaj: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Oznacza to, że lepiej jest w warstwie transportowej. Jeśli chodzi o abstrakcję, stwierdziłem, że użycie wzorca Ogólnego repozytorium działa dobrze, gdy potrzebowałem wstrzyknąć EntityType, ale nadal chciałem zachować wspólne metody. Z drugiej strony sam argumentowałem na forum MSDN LINQ, że EF jest wzorcem repozytorium, ponieważ wszystko jest w pamięci. W jednym projekcie wykorzystano liczne klauzule Where jako wywołania metod, które działały dobrze.
John Peters,
Ale jednym z obszarów, które przyjrzałem się, ale które odrzuciłem, był biznes Drzewo Ekspresji, niektóre naprawdę go lubią, ale ... musisz się go dokładnie przestudiować, zanim okaże się przydatny.
John Peters,
2
powinniśmy wrócić do wywoływania SQL z kontrolerów
Bob

Odpowiedzi:

12

Myślę, że łączysz repozytoria i repozytoria ogólne.

Podstawowe repozytorium po prostu łączy Twój magazyn danych i zapewnia metody zwracania danych

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

Nie przecieka warstwy danych do twojego kodu poprzez IQueryable lub inne sposoby przekazywania losowych zapytań i zapewnia dobrze zdefiniowaną testowalną i wstrzykiwalną powierzchnię metod.

Ogólne repozytorium pozwala przekazać zapytanie podobnie jak ORM

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

Zgadzam się, że nie ma sensu używać Ogólnego repozytorium na ORM, które jest po prostu kolejnym Ogólnym repozytorium.

Odpowiedzią jest użycie podstawowego wzorca repozytorium do ukrycia ORM

Ewan
źródło
1
ORM ORM? Starałem się być zabawny. Dlaczego potrzebujesz wyodrębnić ORM?
Johnny
1
z tego samego powodu, dla którego cokolwiek streszczacie. aby uniknąć zanieczyszczenia twojego kodu zastrzeżonymi klasami
Ewan
6

Większość argumentów, o których wspominasz, błędnie przypisuje funkcje wzorca repozytorium, których nie ma.

Pod względem koncepcyjnym repozytorium, jak pierwotnie zdefiniowano w DDD, to tylko zbiór obiektów, które można wyszukiwać lub dodawać do nich. Mechanizm utrzymywania się za nim jest wyodrębniony, więc jako konsument masz złudzenie, że jest to kolekcja w pamięci.

Implementacja repozytorium z nieszczelnymi abstrakcjami ( IQueryablesna przykład ujawnianie ) jest słabą implementacją repozytorium.

Implementacja repozytorium, która ujawnia więcej niż tylko operacje gromadzenia (na przykład funkcje jednostki pracy), jest słabą implementacją repozytorium.

Czy istnieją alternatywy dla repozytorium dostępu do danych? Tak, ale nie są one związane z problemami, które poruszasz w swoim pytaniu.

guillaume31
źródło
1

Dla mnie repozytoria w połączeniu z ORM lub innymi warstwami trwałości DB mają następujące wady:

  1. Ukrywanie jednostek pracy. UoW musi zostać zakodowany przez programistę i rzadko może być zaimplementowany jako rodzaj magii w tle, gdzie użytkownik po prostu dokonuje zapytań i modyfikacji, bez definiowania granic UoW i ewentualnie punktu zatwierdzenia. Czasami UoW porzuca się, redukując je do mikro UoW (np. Sesje NHibernate) w każdej metodzie dostępu do repozytorium.
  2. Ukrywanie lub, w najgorszym przypadku, niszczenie trwałej ignorancji: metody takie jak „Load ()”, „Get ()”, „Save ()” lub „Update ()” sugerują natychmiastowe operacje na pojedynczych obiektach, jakby wysyłane były pojedyncze osoby SQL / DML lub jak przy pracy z plikami. W rzeczywistości, na przykład, metody NHibernate z tymi wprowadzającymi w błąd nazwami zwykle nie zapewniają indywidualnego dostępu, lecz oczekują na leniwe ładowanie lub wstawianie / aktualizowanie partii (ignorowanie trwałości). Czasami programiści zastanawiają się, dlaczego nie uzyskują natychmiastowych operacji DB i siłą przerywają ignorancję uporczywości, tym samym zabijając wydajność i podejmując znaczne wysiłki, aby faktycznie pogorszyć system (znacznie!).
  3. Niekontrolowany wzrost. Proste repozytorium może gromadzić coraz więcej metod w celu dopasowania do konkretnych potrzeb.

Jak na przykład:

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. Obiekt Danger of God: możesz pokusić się o utworzenie jednej klasy boga, obejmującej cały model lub warstwę dostępu do danych. Klasa repozytorium zawierałaby nie tylko metody Car, ale także metody dla wszystkich encji.

Moim zdaniem lepiej jest oferować przynajmniej kilka możliwości zapytania, aby uniknąć ogromnego bałaganu wielu metod jednocelowych. Bez względu na to, czy jest to LINQ, własny język zapytań, czy nawet coś wzięte bezpośrednio z ORM (OK, rodzaj problemu sprzęgania ...).

Erik Hart
źródło
4
Gdzie idą wszystkie te metody, jeśli nie używa się Wzorca Repozytorium?
Johnny
1

Jeśli celem interfejsu repozytorium jest wyśmiewanie bazy danych w celu przeprowadzenia najbardziej jednoznacznego (= testowania w izolacji), najlepszą abstrakcją jest coś, co łatwo wyśmiewać.

Trudno jest wyśmiewać interfejs repozytorium oparty na wyniku IQueryable.

Z punktu widzenia testów jednostkowych

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

można łatwo wyśmiewać

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

można łatwo wyśmiewać tylko wtedy, gdy próbka ignoruje treść parametru zapytania.

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

nie można go łatwo wyśmiewać

k3b
źródło
0

Nie sądzę, że wzorzec repozytorium jest przesadzony, jeśli używasz funkcji lambda do tworzenia zapytań. Zwłaszcza gdy musisz wyodrębnić ORM (moim zdaniem zawsze powinieneś), nie będę obchodził szczegółów implementacyjnych samego repozytorium.

Na przykład:

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
źródło