Entwurfsmuster, die ich oft vermeide: Repository-Muster

Entwurfsmuster bieten bewährte Lösungen für Probleme der realen Welt, mit denen Software-Designs konfrontiert sind. Das Repository-Muster wird verwendet, um die Geschäftslogik und die Datenzugriffsebenen in Ihrer Anwendung zu entkoppeln.

Die Datenzugriffsschicht enthält normalerweise speicherspezifischen Code und Methoden zum Bearbeiten der Daten zum und vom Datenspeicher. Die Datenzugriffsschicht, die das Repository abstrahiert, kann ein ORM (dh Entity Framework oder NHibernate), eine XML-Datei, ein Webdienst usw. sein. Es kann sich sogar um eine Sammlung von SQL-Anweisungen handeln.

Bei der Verwendung des Repository-Entwurfsmusters muss die Geschäftslogikschicht Ihrer Anwendung keine Kenntnisse darüber haben, wie die Datenpersistenz darunter abläuft. Im Wesentlichen vermittelt ein Repository zwischen der Domäne und den Datenzuordnungsebenen Ihrer Anwendung. Es soll Ihnen eine Kapselung darüber geben, wie Daten tatsächlich in der Datenspeicherschicht gespeichert werden.

Das Repository-Muster kann nützlich sein, wenn Sie viele Entitäten und viele komplexe Abfragen haben, um mit diesen Entitäten zu arbeiten. Eine zusätzliche Abstraktionsebene kann in diesem Fall dazu beitragen, doppelte Abfragelogik zu vermeiden.

Das generische Repository

Ein generisches Repository ist ein Typ, der aus einer Reihe generischer Methoden zum Ausführen von CRUD-Operationen besteht. Es ist jedoch nur ein weiteres Anti-Muster und wird häufig mit Entity Framework verwendet, um Aufrufe an die Datenzugriffsschicht zu abstrahieren. Meiner Meinung nach ist die Verwendung eines generischen Repositorys eine zu weitgehende Verallgemeinerung. Es ist eine schlechte Idee, Aufrufe von Entity Framework mithilfe eines generischen Repositorys zu abstrahieren.

Lassen Sie mich dies anhand eines Beispiels erklären.

Die folgende Codeliste zeigt ein generisches Repository - es enthält generische Methoden zum Ausführen der grundlegenden CRUD-Operationen.

public interface IRepository

   {

       IEnumerable GetAll();

       T GetByID(int id);

       void Add(T item);

       void Update(T item);

       void Delete(T item);

   }

Um ein bestimmtes Repository zu erstellen, müssten Sie dann die generische Schnittstelle implementieren, wie in der folgenden Codeliste gezeigt.

public class AuthorRepository : IRepository

   {

       //Implemented methods of the IRepository interface

   }

Wie Sie sehen können, müssen Sie zum Erstellen einer bestimmten Repository-Klasse jede der Methoden der generischen Repository-Schnittstelle implementieren. Der Hauptnachteil dieses Ansatzes besteht darin, dass Sie für jede Entität ein neues Repository erstellen müssen.

Ein weiterer Nachteil dieses Ansatzes: Die grundlegende Absicht des Repository-Musters besteht darin, Ihre Domänenschicht davon zu entkoppeln, wie die Daten tatsächlich von der Datenzugriffsschicht beibehalten werden. Hier ist eine aktualisierte Version der soeben erstellten Repository-Klasse.

public class AuthorRepository : IRepository

   {

       private AuthorContext dbContext;

       //Methods of the IRepository interface

   }

Wie Sie in der zuvor angegebenen Codeliste sehen können, benötigt das AuthorRepository die AuthorContext-Instanz, um die CRUD-Operationen auszuführen, für die es bestimmt ist. Wo ist also die Entkopplung? Idealerweise sollte die Domänenschicht keine Kenntnis der Persistenzlogik haben.

Eine zusätzliche Abstraktionsebene

Das Domänenmodell und das Persistenzmodell in einer Anwendung haben deutlich unterschiedliche Verantwortlichkeiten. Während das erstere das Verhalten modelliert, dh die realen Probleme und die Lösungen für diese Probleme modelliert, wird das letztere verwendet, um zu modellieren, wie die Daten der Anwendung tatsächlich im Datenspeicher gespeichert sind.

Die Absicht des Repository-Musters sollte darin bestehen, die Persistenzlogik zu abstrahieren und die internen Implementierungen der Persistenz der Daten zu verbergen. Die Operationen des Repositorys sollten aussagekräftig genug und nicht generisch sein. Sie können kein generisches Repository haben, das Vorgänge enthalten kann, die in jedes Szenario passen. Dies wird zu einer unnötigen Abstraktion und macht das generische Repository-Muster daher zu einem Anti-Muster. Sie können alle Ihre Domänenobjekte auf dieselbe Weise modellieren. Ein generisches Repository definiert keinen aussagekräftigen Vertrag, und Sie benötigen erneut ein bestimmtes Repository, das Ihr generisches Repository erweitert und die spezifischen Vorgänge bereitstellt, die für diese bestimmte Entität von Bedeutung sind.

Warum benötigen Sie diese zusätzliche Abstraktionsebene überhaupt, da Sie über einige ausgereifte Datenpersistenztechnologien (NHibernate, Entity Framework usw.) verfügen? Die meisten der heute verfügbaren ausgereiften ORM-Technologien verfügen über dieselben Funktionen. Wenn Sie versuchen, ein Repository zu verwenden, fügen Sie einfach ohne Grund eine zusätzliche Abstraktionsebene hinzu. Als Beispiel benötigen Sie möglicherweise Methoden wie die folgenden für Ihr AuthorRepository.

FindAuthorById()

FindAuthorByCountry()

Dies wird noch schlimmer, da Sie immer mehr Methoden und komplexe Suchvorgänge haben. Am Ende steht Ihnen ein Repository zur Verfügung, das eng mit der darunter verwendeten persistenten Speicherschicht verknüpft ist.