Was ist eine bessere Abstraktion, wenn das Repository-Muster für moderne ORMs (EF, nHibernate) übertrieben ist?

12

Ich habe kürzlich viele Argumente gegen die Verwendung des Repository-Musters mit leistungsstarken ORMs wie Entity Framework gelesen, da es neben der Unit of Work-Funktionalität auch Repository-ähnliche Funktionen enthält.

Ein weiteres Argument gegen die Verwendung des Musters für eine Situation wie Unit-Tests ist, dass das Repository-Muster eine undichte Abstraktion ist, da die allgemeineren Implementierungen IQueryable nutzen.

Die Argumente gegen die Verwendung des Repository-Musters sind für mich sinnvoll, aber die vorgeschlagenen alternativen Methoden für Abstraktionen sind oft verwirrender und erscheinen genauso übertrieben wie das Problem.

Die Lösung von Jimmy Bogards scheint eine Mischung aus dem Wegblasen der Abstraktionen, aber auch der Einführung seiner eigenen Architektur zu sein. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Ein weiteres Beispiel dafür, dass Repositorys unnötig sind ... aber benutze meine Architektur! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

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

Ich habe keinen eindeutigen Ersatz oder eine Alternative zum "übermäßig komplexen" Repository-Muster-Ansatz gefunden, der selbst nicht mehr architektonisch ist.

AnotherDeveloper
quelle
4
Was genau wollen Sie erreichen? Abstraktionen sollten einen Zweck haben. Wenn Sie eine CRUD-App schreiben, ist ein ORM wahrscheinlich abstrakt genug.
JacquesB
@JacquesB Ich versuche, Probleme mit der relationalen Impedanz von Objekten mit einem robusten Domänenmodell zu vermeiden, aber abstrahiere dies auch von meinen Ansichtsmodellen in einer MVC-Implementierung.
AnotherDeveloper
Reed Cospey hat hier viel Positives über IQuerable zu sagen: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Dies impliziert, dass es auf der Transportschicht besser ist. Was die Abstraktion angeht, fand ich, dass die Verwendung des generischen Repository-Musters gut funktioniert, wenn ich den EntityType injizieren musste, aber dennoch gemeinsame Methoden beibehalten wollte. Andererseits habe ich selbst im MSDN LINQ-Forum argumentiert, dass EF ein Repository-Muster ist, weil alles im Speicher ist. Ein Projekt verwendete zahlreiche Where-Klauseln als Methodenaufrufe, die gut funktionierten.
John Peters
Aber ein Bereich, den ich untersucht, aber abgelehnt habe, war das Expression Tree-Geschäft, einige mögen es wirklich, aber ... Sie müssen es gründlich studieren, bevor Sie es nützlich finden.
John Peters
2
Wir sollten zurückgehen, um SQL von Controllern aus
aufzurufen

Antworten:

12

Ich denke, Sie verschmelzen Repositories und generische Repositories.

Ein Basis-Repository verbindet lediglich Ihren Datenspeicher und bietet Methoden zum Zurückgeben der Daten

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

Die Datenschicht wird nicht über IQueryable oder andere Methoden zur Übergabe zufälliger Abfragen in Ihren Code übertragen und bietet eine genau definierte testbare und injizierbare Oberfläche von Methoden.

Mit einem generischen Repository können Sie Ihre Abfrage ähnlich wie mit einem ORM übergeben

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

Ich bin damit einverstanden, dass es nicht viel Sinn macht, ein generisches Repository über einem ORM zu verwenden, das im Grunde nur ein anderes generisches Repository ist.

Die Antwort besteht darin, das grundlegende Repository-Muster zu verwenden, um Ihr ORM auszublenden

Ewan
quelle
1
Ein ORM eines ORM? Ich habe versucht lustig zu sein. Warum müssen Sie das ORM abstrahieren?
Johnny
1
aus demselben Grund abstrahieren Sie alles. um zu vermeiden, dass Ihr Code mit proprietären Klassen verschmutzt wird
Ewan
6

Die meisten der Argumente, die Sie erwähnen, werden fälschlicherweise den nicht vorhandenen Repository-Musterfunktionen zugeordnet.

Konzeptionell ist ein Repository, wie es ursprünglich in DDD definiert wurde, nur eine Sammlung von Objekten, die Sie suchen oder hinzufügen können. Der dahinter stehende Persistenzmechanismus wird abstrahiert, sodass Sie als Verbraucher die Illusion bekommen, dass es sich um eine In-Memory-Sammlung handelt.

Eine Repository-Implementierung mit undichten Abstraktionen ( IQueryablesz. B. Offenlegung ) ist eine schlechte Repository-Implementierung.

Eine Repository-Implementierung, die mehr als nur Erfassungsvorgänge (z. B. Unit of Work-Funktionen) verfügbar macht, ist eine schlechte Repository-Implementierung.

Gibt es Alternativen zum Repository für den Datenzugriff? Ja, aber sie beziehen sich nicht auf die Themen, die Sie in Ihrer Frage ansprechen.

guillaume31
quelle
1

Für mich haben Repositorys in Kombination mit ORM oder anderen DB-Persistenzschichten folgende Nachteile:

  1. Arbeitseinheiten vertuschen. UoW muss vom Programmierer codiert werden und kann selten als eine Art Magie im Hintergrund implementiert werden, bei der der Benutzer einfach Abfragen und Änderungen vornimmt, ohne die UoW-Grenzen und möglicherweise den Festschreibungspunkt zu definieren. Manchmal werden die UoW aufgegeben, indem sie in jeder Repository-Zugriffsmethode in Mikro-UoW (z. B. NHibernate-Sitzungen) reduziert werden.
  2. Vertuschen oder im schlimmsten Fall Zerstören der Persistenz-Ignoranz: Methoden wie "Load ()", "Get ()", "Save ()" oder "Update ()" schlagen sofortige Einzelobjektoperationen vor, als ob sie eine Person senden würden SQL / DML oder als würde man mit Dateien arbeiten. Tatsächlich stellen beispielsweise die NHibernate-Methoden mit diesen irreführenden Namen normalerweise keinen individuellen Zugriff bereit, sondern stellen sich in die Warteschlange für verzögertes Laden oder Einfügen / Aktualisieren von Batch (Persistence Ignorance). Manchmal fragen sich Programmierer, warum sie keine sofortigen DB-Operationen erhalten und die Unwissenheit über die Persistenz gewaltsam auflösen, wodurch die Leistung beeinträchtigt und große Anstrengungen unternommen werden, um das System tatsächlich (viel!) Schlechter zu machen.
  3. Unkontrolliertes Wachstum. In einem einfachen Repository werden möglicherweise immer mehr Methoden gesammelt, um bestimmten Anforderungen gerecht zu werden.

Sowie:

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. Objekt "Gefahr Gottes": Sie könnten versucht sein, eine Gottklasse zu erstellen, die Ihr gesamtes Modell oder Ihre Datenzugriffsebene abdeckt. Die Repository-Klasse würde nicht nur Car-Methoden enthalten, sondern Methoden für alle Entitäten.

Meiner Meinung nach ist es besser, zumindest einige Abfragemöglichkeiten anzubieten, um das große Durcheinander vieler Einzweckmethoden zu vermeiden. Egal, ob es sich um LINQ, eine eigene Abfragesprache oder etwas handelt, das direkt aus dem ORM stammt (OK, Art des Kopplungsproblems ...).

Erik Hart
quelle
4
Wohin gehen all diese Methoden, wenn man das Repository-Muster nicht verwendet?
Johnny
1

Wenn der Zweck der Repository-Schnittstelle darin besteht, die Datenbank für einen unittest (= Test isoliert) zu verspotten, ist die beste Abstraktion etwas, das leicht zu verspotten ist.

Es ist schwierig, eine Repository-Schnittstelle zu verspotten, die auf einem IQueryable-Ergebnis basiert.

Aus Sicht der Unit-Tests

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

kann leicht verspottet werden

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

kann nur dann leicht verspottet werden, wenn der Verspottung den Inhalt des Abfrageparameters ignoriert.

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

kann nicht leicht verspottet werden

k3b
quelle
0

Ich denke nicht, dass das Repository-Muster übertrieben ist, wenn Sie Lambda-Funktionen zum Abfragen verwenden. Besonders wenn Sie das ORM abstrahieren müssen (meiner Meinung nach sollten Sie es immer tun), sind mir die Implementierungsdetails des Repositorys selbst egal.

Beispielsweise:

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
quelle