2009-09-11 16 views
35

Ich habe BDD/DDD untersucht und als Konsequenz versucht, eine korrekte Implementierung des Repository-Musters zu finden. Bisher war es schwierig, einen Konsens darüber zu finden, wie dies am besten umgesetzt werden kann. Ich habe versucht, es auf die folgenden Variationen herunterzukochen, aber ich bin mir nicht sicher, welches der beste Ansatz ist.Die beste Methode zum Implementieren von Repository Pattern?

Als Referenz erstellen ich eine ASP.MVC-Anwendung mit NHibernate als Back-End.

public interface IRepository<T> { 
     // 1) Thin facade over LINQ 
     T GetById(int id); 
     void Add(T entity); 
     void Update(T entity); 
     void Remove(T entity); 
     IQueryable<T> Find(); 
     // or possibly even 
     T Get(Expression<Func<T, bool>> query); 
     List<T> Find(Expression<Func<T, bool>> query); 
} 

public interface IRepository<T> { 
     // 2) Custom methods for each query 
     T GetById(int id); 
     void Add(T entity); 
     void Update(T entity); 
     void Remove(T entity); 
     IList<T> FindAll(); 
     IList<T> FindBySku(string sku); 
     IList<T> FindByName(string name); 
     IList<T> FindByPrice(decimal price); 
     // ... and so on 
} 

public interface IRepository<T> { 
     // 3) Wrap NHibernate Criteria in Spec pattern 
     void Add(T entity); 
     void Update(T entity); 
     void Remove(T entity); 
     IList<T> FindAll(); 
     IList<T> FindBySpec(ISpecification<T> specification); 
     T GetById(int id); 
} 


public interface IRepository<T> { 
     // 4) Expose NHibernate Criteria directly 
     T GetById(int id); 
     void Add(T entity); 
     void Update(T entity); 
     void Remove(T entity); 
     IList<T> FindAll(); 
     IList<T> Find(ICriteria criteria); 
     // .. or possibly 
     IList<T> Find(HQL stuff); 
} 

Meine ersten Gedanken sind, dass

1) aus einem Wirkungsgrad Sicht ist groß, aber ich kann in Schwierigkeiten geraten, wie die Dinge komplizierter.

2) scheint sehr langweilig und könnte mit einer sehr überfüllten Klasse enden, bietet aber ansonsten einen hohen Grad an Trennung zwischen meiner Domänenlogik und Datenschicht, die ich mag.

3) scheint im Voraus schwierig und mehr Arbeit, um Abfragen zu schreiben, sondern grenzt Kreuzkontamination auf nur die Specs-Ebene.

4) Meine am wenigsten bevorzugten, aber möglicherweise direkteste Implementierung und möglicherweise die meisten Datenbank effizient für komplexe Abfragen, obwohl es eine große Verantwortung für den aufrufenden Code.

+0

+1 für eine rechtzeitige Frage. Ich habe auch mit dem Repository-Muster gekämpft. – TrueWill

+3

Wahrscheinlich nicht das, wonach Sie suchen, aber ich fand diese Implementierung des Spec-Musters wirklich cool: http://mathgeekcoder.blogspot.com/2008/07/advanced-domain-model-queries-using.html. Vor allem, weil Sie damit Linq2Sql/Linq2NHibernate/etc. –

+0

Danke TrueWill und danke Martinho, das bezieht sich eigentlich sehr gut auf ein anderes Thema, das ich kürzlich http: // stackoverflow veröffentlicht habe.com/questions/1408553/Spezifikation-Muster-vs-spec-in-bdd –

Antwort

10

Ich denke, sie sind alle gute Optionen (außer vielleicht 4, wenn Sie sich nicht mit Nhibernate binden wollen), und Sie scheinen Pro und Kontra gut analysiert, um eine Entscheidung auf eigene Faust auf der Grundlage Ihrer aktuellen Bemühungen zu treffen . Schlagen Sie sich nicht auch hart auf diese.

ich zur Zeit auf einer Mischung zwischen 2 und 3 Arbeits denke, ich:

public interface IRepository<T> 
{ 
     ... 
     IList<T> FindAll(); 
     IList<T> FindBySpec(ISpecification<T> specification); 
     T GetById(int id); 
} 

public interface ISpecificRepository : IRepository<Specific> 
{ 
     ... 
     IList<Specific> FindBySku(string sku); 
     IList<Specific> FindByName(string name); 
     IList<Specific> FindByPrice(decimal price); 
} 

Und es gibt auch einen Repository (T) Basisklasse.

+1

Ich vermute, ein wenig von # 2 wird in jede Lösung kriechen. –

1

Ich bin ein Bif-Fan von 1, weil ich Filter und Seitenerweiterungsmethoden erstellen kann, als ich auf die IQueryable <> Rückgabewerte für die Find-Methode anwenden kann. Ich behalte die Erweiterungsmethoden in der Datenschicht und konstruiere dann direkt in der Business-Schicht. (Nicht völlig rein, zugegeben.)

Natürlich, wenn das System stabilisiert habe ich die Möglichkeit, spezifische Find-Methoden mit den gleichen Erweiterungsmethoden zu machen und optimieren mit Func <>.

5

Eines der Dinge, die wir tun, ist, dass alle unsere Repositories unterschiedliche Bedürfnisse haben, so werden wir eine Sammlung von Schnittstellen zu schaffen:

public interface IReadOnlyRepository<T,V> 
{ 
    V Find(T); 
} 

In diesem Beispiel wird die Nur-Lese-Repository nur tut wird aus der Datenbank. Der Grund für die T, V ist die V darstellt, was durch das Repository zurückgeführt wird und die T repräsentiert, was in übergeben wird, so könnte man so etwas tun:

public class CustomerRepository:IReadOnlyRepository<int, Customer>, IReadOnlyRepository<string, Customer> 
{ 
    public Customer Find(int customerId) 
    { 
    } 

    public Customer Find(string customerName) 
    { 
    } 
} 

ich auch separate Schnittstellen für das Add erstellen , Aktualisieren und Löschen. Auf diese Weise, wenn mein Repository das Verhalten nicht benötigt, implementiert es die Schnittstelle nicht.

+0

Das ist interessant. Ich muss über die möglichen Vorteile der Verwendung von IReadonlyRepository nachdenken, aber auf den ersten Blick mag ich es irgendwie. Danke für das Teilen. –

+0

Wenn Sie das nützlich finden, können Sie meine Antwort abstimmen? –

1

Bei der Verwendung von NH mit Linq können Ihr Repository sein:

session.Linq<Entity>() 

Die Spezifikationen sind Dinge, die mit umgehen:

IQueryable<Entity> 

Sie alles weg Fassade können, wenn Sie wollen, aber das ist ein Viel mondäne Arbeit, um eine Abstraktion zu abstrahieren.

Einfach ist gut. Yah, NH tut Datenbanken, aber es bietet so viele weitere Muster. Dass andere als die DAL von NH abhängen ist weit entfernt von einer Sünde.

+0

Aber die Verwendung der Sitzung direkt in der Logik, obwohl einfach, würde die Inversion der Kontrolle verletzen und es wirklich schwierig machen zu testen, würde es nicht? –

+0

Hängt davon ab, wie Sie Ihren Code strukturieren. Wenn Sie das Muster der Arbeitseinheit verwenden, ist ISession Ihre UOW. Sie können eine dünne Fassade über ISession legen, wenn das die Dinge besser macht (ich nicht). Sie haben dann also Repositories. Ich betrachte Repositories als sehr einfach - Objekt holen, Objekt speichern, Objekte als Sammlung darstellen (ISession macht das alles). Abfragen/Spezifikationen können von IQueryable verarbeitet werden. Wenn Sie eine Domänenlogik haben, die abhängig von ISession endet, wäre sie stattdessen abhängig von einem Repository gelandet. Beides sind gleichermaßen schlechte Situationen. – anonymous

+0

Also ISession kann ein bisschen eine Art "alles" Schnittstelle sein. Aus diesem Grund kann es etwas expliziter sein, wie es verwendet wird (IUnitOfWork, IRepository , etc). Ich denke, das ist eine persönliche Vorliebe, und ich würde nicht viel Zeit darauf verwenden, die perfekte Abstraktion zu konstruieren. – anonymous

22

Es gibt auch ein gutes Argument für ein „keine der oben genannten“ -Ansatz.

Das Problem mit generischen Repositories besteht darin, dass Sie davon ausgehen, dass alle Objekte in Ihrem System alle vier CRUD-Operationen unterstützen: Erstellen, Lesen, Aktualisieren, Löschen. In komplexen Systemen haben Sie jedoch wahrscheinlich Objekte, die nur wenige Operationen unterstützen. Zum Beispiel könnten Sie Objekte haben, die schreibgeschützt sind, oder Objekte, die erstellt, aber nie aktualisiert werden.

Sie könnten die IRepository-Schnittstelle in kleine Schnittstellen für Read, Delete usw. brechen, aber das wird ziemlich schnell unordentlich.

Gregory Young ist ein gutes Argument (aus DDD-/Software-Layering-Perspektive), dass jedes Repository nur die Operationen unterstützen soll, die für das Domänenobjekt oder Aggregat, mit dem Sie arbeiten, spezifisch sind. Hier ist sein Artikel auf generic repositories.

Und für eine alternative Ansicht, siehe dieses Ayende blog post.

+1

stimme ich zu. Ich würde nur hinzufügen, dass dies nicht bedeutet, dass Sie keine generische Implementierung haben können, Sie sollten einfach keine generische Schnittstelle haben. Sie können ein abstraktes Repository (und kein IRepository Interface) mit allen Methoden geschützt haben. Jedes spezifische Repository kann es dann erweitern und gewünschte Methoden öffentlich machen, um die ISpecificRepository-Schnittstelle zu implementieren. – svallory

+0

In der Tat! Nach meiner Erfahrung ist es am besten, das Repository-Muster zu implementieren, um es überhaupt nicht zu implementieren. Verwenden Sie stattdessen eine gute ORM-API (z. B. JPA in Java) und eine einzelne Klasse "ApplicationDatabase", um die Verwendung zu vereinfachen. Das ist sowieso meine Entscheidung für eine Java EE App; Ich finde, dass es den Anwendungscode einfacher, kürzer und zusammenhängender hält als ein paar zusätzliche "EntityAbcRepository" -Klassen. –

1

Ich glaube, es hängt von Ihren Bedürfnissen ab. Es ist wichtig, über Repository in Verbindung mit anderen Entwurfsmustern nachzudenken, die Sie in Betracht ziehen. Letztendlich hängt es sehr davon ab, was Sie vom Repository erwarten (was sind die Hauptgründe für die Verwendung).

Müssen Sie eine strikte Schicht erstellen (z. B. müssen Sie in Zukunft NHibernate durch Entity Framework ersetzen)? Möchten Sie einen Test speziell für die Repository-Methoden schreiben?

Es gibt keine beste Möglichkeit, Repository zu erstellen. Es gibt nur ein paar Wege und es liegt definitiv an Ihnen, was für Ihre Bedürfnisse am praktischsten ist.

Verwandte Themen