2009-09-11 9 views
35

Badałem BDD/DDD i w konsekwencji starałem się wymyślić właściwą implementację wzorca repozytorium. Do tej pory trudno było osiągnąć konsensus co do najlepszego sposobu wdrożenia tego. Próbowałem sprowadzić to do następujących odmian, ale nie jestem pewien, które podejście jest najlepsze.Najlepszy sposób na wdrożenie wzoru repozytorium?

Dla odniesienia buduję aplikację ASP.MVC z NHibernate jako 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); 
} 

Moje pierwsze myśli, które

1) jest świetny z punktu widzenia efektywności, ale mogę mieć kłopoty, jak robi się bardziej skomplikowana.

2) wydaje się bardzo żmudna i może zakończyć się bardzo zatłoczoną klasą, ale poza tym oferuje wysoki stopień separacji między moją logiką domeny i warstwą danych, które lubię.

3) wydaje się trudne z góry i więcej pracy, aby pisać zapytania, ale ogranicza krzyżowanie zanieczyszczeń tylko do warstwy Specs.

4) Moja najmniej ulubiona, ale prawdopodobnie najbardziej bezpośrednia implementacja i prawdopodobnie najbardziej wydajna baza danych dla złożonych zapytań, jednak nakłada dużą odpowiedzialność na kod wywołujący.

+0

+1 za aktualne pytanie. Zmagałem się również ze schematem repozytorium. – TrueWill

+3

Prawdopodobnie nie to, czego szukasz, ale stwierdziłem, że ta implementacja wzoru Spec naprawdę jest fajna: http://mathgeekcoder.blogspot.com/2008/07/advanced-domain-model-queries-using.html. Głównie dlatego, że pozwala używać Linq2Sql/Linq2NHibernate/etc. –

+0

Dzięki TrueWill i dzięki Martinho, które bardzo dobrze odnoszą się do innego tematu, który niedawno opublikowałem http: // stackoverflow.com/questions/1408553/specyfikacja-wzór-kontra-spec-in-bdd –

Odpowiedz

10

Myślę, że wszystkie są dobrym rozwiązaniem (z wyjątkiem może 4, jeśli nie chcesz przywiązywać się do nhibernate), i wydajesz się, że masz dobre i złe strony, które są dobrze przeanalizowane, aby podjąć decyzję na własną rękę w oparciu o twoje obecne starania. . Nie bój się zbyt trudne na tym.

Obecnie pracuję na mieszaninie między 2 i 3 Chyba:

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); 
} 

I tam także Repository (T) klasy bazowej.

+1

Podejrzewam, że odrobina nr 2 wkradnie się do każdego rozwiązania. –

1

Jestem wentylatorem bif 1, ponieważ mogę tworzyć filtry i metody rozszerzenia stronicowania, niż mogę zastosować do wartości zwracanych przez IQueryable < dla metody Znajdź. Zachowuję metody rozszerzenia w warstwie danych, a następnie konstruuję w locie w warstwie biznesowej. (Niezupełnie czysty, oczywiście.)

Oczywiście, kiedy system się stabilizuje, mam możliwość wyboru określonych metod wyszukiwania przy użyciu tych samych metod rozszerzenia i zoptymalizowania za pomocą Func <>.

5

Jedną z rzeczy, które robimy, jest to, że wszystkie nasze repozytoria mają różne potrzeby, dlatego tworzymy zbiór interfejsów:

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

w tym przypadku tylko do odczytu repozytorium tylko dokłada pobiera z bazy danych. Powodem, T, V to V oznacza co jest zwracane przez repozytorium, a T reprezentuje to, co jest przekazywane w, więc można zrobić coś takiego:

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

    public Customer Find(string customerName) 
    { 
    } 
} 

mogę też utworzyć oddzielne interfejsy dla Dodawanie , Aktualizuj i usuń. W ten sposób, jeśli moje repozytorium nie potrzebuje tego zachowania, po prostu nie implementuje interfejsu.

+0

To jest interesujące. Muszę pomyśleć o potencjalnych korzyściach z używania IReadonlyRepository , ale na pierwszy rzut oka lubię to. Dziękuję za udostępnienie. –

+0

Jeśli uznasz to za przydatne, możesz oddać głos w mojej odpowiedzi? –

1

Podczas korzystania NH z Linq repozytorium mogą być:

session.Linq<Entity>() 

Specyfikacje są rzeczy, które dotyczą:

IQueryable<Entity> 

Można to wszystko fasada dalej, jeśli chcesz, ale to dużo przyziemnej pracy na abstrakcję.

Proste jest dobre. Yah, NH robi bazy danych, ale dostarcza o wiele więcej wzorów. Posiadanie innych niż DAL zależy od NH jest dalekie od grzechu.

+0

Ale używanie sesji bezpośrednio w logice, choć proste, naruszyłoby Inversion of Control i sprawiłoby, że naprawdę trudno byłoby przetestować, czyż nie? –

+0

W zależności od struktury kodu. Jeśli używasz wzorca Jednostki Pracy, to ISYCJA jest twoją własnością. Możesz umieścić cienką fasadę na ISession, jeśli to poprawia sytuację (ja nie). Więc ty zostajesz z repozytoriami. Przeglądam repozytorium jako bardzo podstawowe - pobierz obiekt, zapisz obiekt, pokaż obiekty jako kolekcję (ISession to wszystko). Zapytania/Specyfikacje mogą być obsługiwane przez IQueryable. Jeśli posiadasz logikę domeny, która kończy się w zależności od ISession, zakończyłaby się w zależności od repozytorium. Obie te sytuacje są równie złe. – anonymous

+0

Tak więc ISession może w końcu być trochę interfejsem typu "zrób wszystko". Dlatego umieszczenie go za innym interfejsem może być nieco bardziej wyraźne na temat tego, w jaki sposób jest on używany (IUnitOfWork, IRepository itd.). Myślę, że ta część to osobiste preferencje, a ja nie poświęciłbym wiele czasu na opracowanie idealnej abstrakcji. – anonymous

22

Jest też dobry argument na „żaden z powyższych” podejścia.

Problem z repozytoriami ogólnymi polega na tym, że zakładasz, że wszystkie obiekty w systemie obsługują wszystkie cztery operacje CRUD: Utwórz, Odczyt, Aktualizuj, Usuń. Ale w złożonych systemach prawdopodobnie będziesz mieć obiekty, które obsługują tylko kilka operacji. Na przykład możesz mieć obiekty, które są tylko do odczytu lub obiekty, które zostały utworzone, ale nigdy nie zostały zaktualizowane.

Możesz przerwać interfejs IRepository na małe interfejsy, do odczytu, usuwania itp., Ale to szybko się brudzi.

Gregory Young robi dobry argument (z perspektywy DDD/oprogramowania), że każde repozytorium powinno obsługiwać tylko operacje specyficzne dla obiektu domeny lub agregatu, z którym pracujesz. Oto jego artykuł na temat generic repositories.

W przypadku alternatywnego widoku zobacz tę Ayende blog post.

+1

Zgadzam się. Dodam tylko, że nie oznacza to, że nie możesz mieć ogólnej implementacji, po prostu nie powinieneś mieć ogólnego interfejsu. Możesz mieć abstrakcyjne repozytorium (i bez interfejsu IRepository ) z chronionymi wszystkimi metodami. Każde konkretne repozytorium może następnie rozszerzyć je i uprościć odpowiednie metody w celu implementacji interfejsu ISpecificRepository. – svallory

+0

Rzeczywiście! Z mojego doświadczenia wynika, że ​​najlepszym sposobem zaimplementowania wzorca repozytorium jest * nie * zaimplementowanie go w ogóle. Zamiast tego użyj dobrego interfejsu API ORM (takiego jak JPA w Javie) i jednej klasy "ApplicationDatabase", aby ułatwić korzystanie z niej. W każdym razie to mój wybór dla aplikacji Java EE; Uważam, że sprawia, że ​​kod aplikacji staje się prostszy, krótszy i bardziej spójny niż posiadanie kilku dodatkowych klas "EntityAbcRepository". –

1

Wierzę, że to zależy od Twoich potrzeb. Ważne jest, aby myśleć o repozytorium w połączeniu z innymi wzorcami projektowymi, które uważasz za użyteczne. Wreszcie zależy to od tego, czego oczekujesz od repozytorium (jakie są główne powody jego używania).

Czy musisz utworzyć warstwę ścisłą (na przykład w przyszłości będziesz musiał zastąpić NHibernate przez Entity Framework)? Czy chcesz napisać test szczególnie dla metod repozytorium?

Nie ma najlepszego sposobu na utworzenie repozytorium. Jest tylko kilka sposobów i zdecydowanie zależy od Ciebie, co jest najbardziej praktyczne w Twoich potrzebach.