Repository Pattern

In diesem Blogpost schreibe ich über Repository Pattern. Ich erkläre kurz was es ist, wozu es nützlich ist und zeige auch mit ein paar Bilder vor, wie es bei mir im TOECHA Projekt aussieht.

Wieso REpository Pattern?

Ich habe dieses Thema genommen, weil das Repository Patter ein sehr wichtiges Pattern ist, was mehrmals benutzt wird und deshalb von einem Programmierer oft verlangt ist. Derzeit arbeite ich am TOECHA Projekt, wo wir vor kurzem erst die Repository Pattern implementiert haben. Wir haben vorher mit dem DbContext gearbeitet, doch das hat uns beim Unit Testing zu Schwierigkeiten gebracht, welche mit den Repository Pattern behoben wurde. Man sollte so früh wie möglich entscheiden, ob man den Repository Pattern implementieren soll oder nicht. Je später man es implementiert, desto mehr code gibt es zum refactoring. Die gleiche Erfahrung habe ich auch gemacht und musste viel Zeit investieren für das refactoring im ganzen Projekt.

Was ist Repository Pattern?

Das Repository Pattern ist eines von vielen Patterns, die es gibt. Vielleicht kennt man auch andere Design Patterns, wie z.B. Builder Pattern oder Observer Pattern usw. Da stösst man zur Regel “program to interfaces, not implementation”. Heisst, man hat ein Interface mit die nötigen Funktionen, was die andere Klassen implementieren und dass man nun als client mit einem Interface object weiterarbeitet. Mit dem Repository Layer erstellen wir eine abstrakte Schicht zwischen dem Data Layer und Business Logic Layer. Es erschafft eine bessere Übersicht und das Code ist einfach wiederverwendbar. Die Logik um auf Daten zuzugreifen oder Daten zu persistieren ist in separaten Klassen aufgeteilt, die Repository genannt werden. Es gibt keine feste Regel, wie das Pattern unbedingt aussehen muss. Man kann es natürlich erweitern, ohne aber dass man die Vorteile von Repository Patterns verliert. 

Pros

  • Seperation of concern
  • Einfacher zu Mocken
  • DRY (Don’t Repeat Yourself); Keine wiederholte Codezeilen

Cons

  • Fügt eine neue abstrakte und komplexe Layer ins Projekt hinzu, was für kleine Applikationen auch ein „overkill“ sein kann

Demo

Im TOECHA Projekt haben wir eine generische Repository Pattern implementiert. Unsere Data Models implementieren das Interface IEntity. Zuerst erstellen wir ein Repository Interface mit nützliche Methoden.

Jede Klasse vom Typ IEntity kann hier eingesetzt werden, heißt es lässt nur Data Models zu, was wir auch wollen. 

Wir erstellen nun eine abstrakte Klasse, wo die Implementation der Methoden folgt. Wir können hier immer den generischen Typ als Entität verwenden.

Die anderen Methoden wurden auch implementiert, dies dient nur als Beispiel.

Nun werden alle Interfaces und Repositories für alle Data Models implementiert. Jede Data Model hat eine Repository Interface und eine Repository Implementation. 

In diesem Beispiel sieht man das Repository Interface der Entität “Player”. Wie im “IRepository” können hier auch Methoden definiert werden, die für die Entität “Player” gebraucht werden. 

Die definierte Methoden können hier in der Implementation von der Repository implementiert werden. Man kann auch Polymorphismus benutzen, falls eine Methode speziell zu implementieren ist. Hier sieht man als Beispiel die Player Repository. Sie vererbt die EfCoreRepository, um die Implementation der Methoden zu haben. Die Repository implementiert auch das IPlayerRepository Interface, um andere definierte Methoden zu implementieren. Die Entität „Player“ hat, im Gegensatz zu den anderen Entitäten, eine GUID als ID. Die implementierte Methode T Find(object id) im EfCoreRepository würde nicht funktionieren, weshalb ich durch Polymorphismus ganz einfach die Methode überschrieben habe. Wie man sieht herrscht hier eine Struktur und die Funktionen sind ganz klar auf Klassen zugeordnet worden. Beim Implementation von neue Funktionen macht man kleine Änderungen und die Objekte kommunizieren miteinander. Da aber jede Data Model eine eigene Repository hat, muss man immer die jeweilige Instanz haben, um auf die Methoden zuzugreifen. Sprich, um die Find() Methode von PlayerRepository aufzurufen, braucht man eine Instanz von IPlayerRepository und um die Find() Methode von TeamRepository aufzurufen, braucht man eine andere Instanz von ITeamRepository. Beim verwenden von mehreren Repositories werden es dann zu viel. 

Deshalb kann man auch ein Repository Wrapper implementieren. Der Repository Wrapper dient, um zu den anderen Repositories zu navigieren.

Im Interface befinden sich nur die Definitionen der Repository Properties.

Nun kann man zu den einzelnen Repositories navigieren und man braucht dafür nur eine Repository Wrapper Instanz.

Da hier mit Interfaces gearbeitet wurde, kann man ganz einfach Dependency Injection implementieren. Dependency Injection wird oben im Repository Wrapper auch benutzt.