Wzorzec repozytorium

0

Witam

Przeglądam wzorce i jak zwykle mam wątpliwości jeżeli chodzi o moje pomysły. Sprawa prosta jak drut, w każdym tutorialu, opisie jest klasa i metody, które zwracają obiekty tej klasy:

http://patryknet.blogspot.com/2010/08/irepository-pattern-wzorzec.html

np. FakePersonRepository ( metoda GetById ) zwraca obiekt klasy Person, ale co w przypadku kiedy moje zapytanie złącza kilka tabel i zawiera kilka pól z tych tabel ( nie tylko pola z jednej), czy interfejs IPersonRepository może zwracać oprócz void-a i T, typ object? ( pewnie może, ale pytanie jak to ma się do dobrych zasad programowania)

3
Ciekawski napisał(a):

co w przypadku kiedy moje zapytanie złącza kilka tabel i zawiera kilka pól z tych tabel ( nie tylko pola z jednej), czy interfejs IPersonRepository może zwracać oprócz void-a i T, typ object? ( pewnie może, ale pytanie jak to ma się do dobrych zasad programowania)

Ale czemu object? Przecież możesz zdefiniować sobie nową klasę, którą będzie zawierała interesujące Cię pola i ją zwracać z repozytorium.

Odrębną kwestią jest bezsens używania wzorca repozytorium w dzisiejszych czasach. To pewno był dobry wzorzec w czasach, gdy nie było ORMów. Wtedy można było za taką fasadą ukryć różne ręcznie sklejane SQLe i tego typu pierdoły. Ale ukrywanie w repozytorium ORMa jest tylko dokładaniem zbędnej warstwy, która nie wnosi chyba żadnej wartości dodanej.

1

To bezpośrednio na DataContext operujesz?

2

Nie zgodzę się, że wartwa z repozytoriami nie ma sensu. Ewentualnie można wyeliminować konkretne repozytoria i wykorzystywać repozytorium generyczne, tj. IRepository<Product>, IRepository<Order> itp, a przy skomplikowanych przypadkach dziedziczyć po IRepository<JakisObiekt> dodając własne, uszczegółowione metody. Według mnie zawsze uzasadnione jest ukrywanie skąd pochodzi źródło danych. Jeżeli zmieni się specyfika (dostęp będzie odbywał się np. poprzez czytanie plików z dysku/sieci) lub wystąpi wymiana ORM, wtedy w prosty sposób sobie poradzimy. Do tego dochodzi testowanie jednostkowego, które IMO nie powinno się odbywać, gdy operujemy bezpośrednio na IQueryable.

0

Czyli jeżeli mam podstawowe klasy, które reprezentują encje i nagle mam widzimisie , że chce zwrócić coś nietypowego ( kilka pól z jednej tabeli i kilka pól z drugiej tabeli ) to muszę tworzyć ku temu osobną klasę tak? Swoją drogą ORM-a nie zawsze każdy używa ( przecież nie jest chyba super wymagany w każdym projekcie ). Ale jeżeli już ORM, to jak to rozwiązać skoro jako Model chcę zastosować mapowane klasy na tabele i każda klasa mapowana ma jakąś logikę.

Pozdrawiam

4
siararadek napisał(a):

To bezpośrednio na DataContext operujesz?

W przypadku Entity Frameworka owszem, w przypadku NHibernate korzystam z odpowiednika czyli Session.

micc napisał(a):

Według mnie zawsze uzasadnione jest ukrywanie skąd pochodzi źródło danych.

A ORM tego nie ukrywa wystarczająco? Z ORMem pracuje się z grubsza tak, jak ze zwykłymi kolekcjami obiektów w pamięci.

Jeżeli zmieni się specyfika (dostęp będzie odbywał się np. poprzez czytanie plików z dysku/sieci) lub wystąpi wymiana ORM, wtedy w prosty sposób sobie poradzimy.

Wiedziałem, że ktoś użyje tego argumentu. :)
A zmieniła się kiedykolwiek w jakimkolwiek projekcie? Ktoś kiedyś zmienił bazę danych na pliki? Zaczynając projekt wybieramy języki, technologie i bazy danych, a potem się tego trzymamy.

Ale nawet jeśli, to co za problem? Wystarczy użyć innego providera do ORMa, i już możemy zmienić bazę danych. Nawet na pliki, jak się uprzemy.

Do tego dochodzi testowanie jednostkowego, które IMO nie powinno się odbywać, gdy operujemy bezpośrednio na IQueryable.

Dlatego powinniśmy pisać tak kod, żeby logikę biznesową dało się przetestować bez udziału bazy danych. Chociaż testy integracyjne nie są niczym złym, wręcz przeciwnie.

Co jest złego w Repository?

Zaczyna się od CustomerRepository z metodami GetCustomerById, GetCustomerByName, GetCustomerByNIP. Wszystko jest fajnie. Potem okazuje się, że trzeba pobrać klienta razem z fakturami, więc ktoś zmienia zawartość tych metod tak, że zachłannie pobierane są faktury. Oczywiście nigdzie w kodzie tego nie widać, do repozytorium nikt inny nie zagląda (bo po co?), ale ludzie dziwią się, że w wielu miejscach aplikacja zwolniła... Na szczęście znalezienie przyczyny nie trwa długo, i trzeba to naprawić. Więc co się robi? Nowe metody o więcej mówiących nazwach: GetCustomerWithInvoicesById, GetCustomerWithInvoicesByName, GetCustomerWithInvoicesByNIP. I jest wszystko dobrze!
Przynajmniej na razie, bo z czasem pojawia się potrzeba pobrania klientów wg adresu wraz z jego pracownikami, ale bez faktur: GetCustomersWithEmployeesButWithoutInvoicesByAddress, a potem potrzeba pobrania wszystkich klientów, z fakturami, bez pracowników, z ulubionymi filmami, bez kochanek wg grupy krwi żony i imienia kota córki prezesa: GetCustomersWithInvoicesWithoutEmployeesWithFavouriteMoviesWithoutMistressesByWifeBloodTypeAndChairmanDaughterCatName.
Im bardziej skomplikowana domena tym więcej potrzeb kombinacji warunków i wyciągania powiązanych encji, tym więcej metod w klasie Repozytorium. Kończymy z klasą z milionem metod... i to ma być dobre?

http://martinfowler.com/eaaCatalog/repository.html

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

A teraz zamieńmy w tej definicji "Repository" z "ORM", i zobaczmy, że nadal będzie ona prawdziwa. Repozytorium ORMowi nie daje absolutnie nic. To tylko kolejna warstwa kodu, która może spowodować błędy.

Ciekawski napisał(a):

Czyli jeżeli mam podstawowe klasy, które reprezentują encje i nagle mam widzimisie , że chce zwrócić coś nietypowego ( kilka pól z jednej tabeli i kilka pól z drugiej tabeli ) to muszę tworzyć ku temu osobną klasę tak?

No raczej... Chyba, że masz inny pomysł, ale to jest generalnie najprostsze. Porządny ORM bez problemu Ci pozwoli wypełnić taką klasę danymi z bazy.

Swoją drogą ORM-a nie zawsze każdy używa ( przecież nie jest chyba super wymagany w każdym projekcie ).

No nie jest wymagany. Ale w zależności od tego, czy płacą nam za godzinę, czy nie, to nie używając ORMa okradamy albo swojego pracodawcę/klienta z pieniędzy, albo siebie z czasu.

Ale jeżeli już ORM, to jak to rozwiązać skoro jako Model chcę zastosować mapowane klasy na tabele i każda klasa mapowana ma jakąś logikę.

Klasy mapowane na tabele to raczej encje, ale mniejsza z tym. Jeśli chcesz zaszywać logikę w tych klasach, to też nie ma problemu, tylko niech to nie będzie logika operacji bazodanowych, jak pytałeś w innym wątku.

1

Te wszystkie GetCustomerByCosTam można zastąpic w prosty sposób repozytorium generycznym.
Ja w wiekszosci projektów uzywam takiego interfejsu, który pozwala tworzyć różne "where'y" i includować navigation properties, kolekcje. Bardzo rzadko (w wyjątkowych przypadkach) muszę tworzyć specyficzne repozytorium.

    
public interface IRepository<TEntity> : IDisposable where TEntity : BaseEntity
    {
        int Count();
        bool Exists(Func<TEntity, bool> expression);
        int Count(Func<TEntity, bool> expression);
        void Delete(TEntity entity);
        void Add(TEntity entity);
        TEntity Get(int Id);
        IEnumerable<TEntity> FindWhere(Func<TEntity, bool> expression, Expression<Func<TEntity, object>> orderExpression, params Expression<Func<TEntity, object>>[] includeExpressions);
        TEntity Single(Func<TEntity, bool> expression);
        IEnumerable<TEntity> GetPaged(Expression<Func<TEntity, bool>> expression, string OrderField, OrderDirection orderDirection, int Take, int Skip, out int TotalFound, params Expression<Func<TEntity, object>>[] includeExpressions);
    }

Operowanie bezpośrednio na dbContext według mnie nie jest dobrym pomysłem, chociażby dlatego, że (pomijając już testy jednostkowe itp.) uzależnia nas od konkretnej technologii w tej warstwie, która powinna operować na abstrakcji. Logika biznesowa powinna operować na rzeczywistych kolekcjach, a nie na czymś, co nie wiemy, czy w ogóle się wykona. I chociaż tak jak wspomniałeś, zazwyczaj nie zmienia się technologii, to eliminacja tej warstwy śmierdzi mi pod względem architektonicznym. Jestem w tym względzie bardzo pedantyczny i na myśl o zrezygnowaniu z tej warstwy włącza mi się czerwone światełko - oho, nie łam zasad separations of concerns, ani abstraction.
Oczywiście dla małych, szybki projektów jestem w stanie zrezygnować z tej warstwy, przy dużych projektach raczej nie.

3
micc napisał(a):

Te wszystkie GetCustomerByCosTam można zastąpic w prosty sposób repozytorium generycznym.

Generyczne repozytorium to antywzorzec. Żeby było śmieszniej sprzeczny z ideę repozytorium. :)
Bo repozytorium to kontrakt między logiką biznesową, a danymi, których ona potrzebuje. Ma zapewnić ścisły zestaw konkretnych (a więc nie generycznych!) metod dostępu do danych, tak aby logika biznesowa nie była świadoma szczegółów tego, co w bazie siedzi, ani jak o to pytać. Repozytorium to kolekcja z zestawem zaawansowanych metod odpytujących, a nie DAO z "Repository" w nazwie.
Repozytorium wystawia metodę GetCustomersByCity(string name), i logiki biznesowej nie obchodzi w tym momencie, czy nazwa miasta to kolumna w tabeli klientów, czy jest oddzielna tabela miast, czy może w ogóle oddzielna tabela adresów. Budując zapytania w warstwie biznesowej i przekazując je do repozytorium, sam pozbawiasz się abstrakcji, o którą tak walczysz.
Generyczne repozytorium może być co najwyżej wewnętrznym helperem dla konkretnych repozytoriów, ale nie interfejsem udostępnionym dla logiki biznesowej.

Logika biznesowa powinna operować na rzeczywistych kolekcjach, a nie na czymś, co nie wiemy, czy w ogóle się wykona.

Co to jest "rzeczywista kolekcja"? I co się nie wykona, bo nie rozumiem? Jak się nie wykona, to chyba aplikacja nie będzie działała.

I chociaż tak jak wspomniałeś, zazwyczaj nie zmienia się technologii, to eliminacja tej warstwy śmierdzi mi pod względem architektonicznym. Jestem w tym względzie bardzo pedantyczny i na myśl o zrezygnowaniu z tej warstwy włącza mi się czerwone światełko - oho, nie łam zasad separations of concerns, ani abstraction.

Tzn. dobra architektura i seperation of concerns polegają na tworzeniu warstw będących prostymi wrapperami i nadawaniu im modnych nazw? :)

I jeszcze takie pytanie - jak w tym generycznym repozytorium zarządzasz transakcjami? Bo nie zauważyłem metody Commit.

0

Mógłby mi ktoś powiedzieć co robią: bool Exists(Func<TEntity, bool> expression);. int Count(Func<TEntity, bool> expression);. Oczywiście czytałem o Func, ale chciałbym poznać zdanie autora czemu wybrał Func i co u niego to robi.

0

Nie uważam, aby repozytorium generyczne było antywzorcem. Do prostych przypadków jest to dobre rozwiązanie.
Jeżeli muszę tworzyć konkretne repozytoria to wtedy faktycznie dziedziczą one po repozytorium generycznym.

Nie widzę różnicy pomiędzy zapisem GetCustomerByCityName("Warszawa") a zapytaniem FindWhere(a=>a.CityName.Equals("Warszawa")).

Zapis ten nie mówi nic o tym, że wewnątrz tej funkcji realizowany będzie dostęp do bazy danych. Jest to de facto zapytanie, które mówi: chcę kolekcję customerów, przefiltrowaną w następujący sposób. Co innego gdybym pisał FindWhere("CityName is Like 'Warszawa'"), wtedy faktycznie uwagi byłyby zasadne.
Zapytania nie buduję w warstwie biznesowej, ponieważ LINQ jest częścią wykorzystywanego języka programowania, a nie warstwy dostępu do danych (jako parametry do metod repozytoriów używane są tylko te wyrażenia, które są zgodne z LINQ to Objects).
Zawsze mogę przejść przykładowo z EF na nHibernate i wyłuskać wymagane search criteria z wyrażenia LINQ.

Mówiąc o "rzeczywistej kolekcji" miałem na myśli operowanie w warstwie biznesnowej na IEnumerable, a nie IQueryable.

Repozytorium u mnie używane jest na poziomie warstwy serwisów aplikacji. W związku z tym, że piszę aplikacje webowe, transakcje są commitowane po zakończeniu pojedynczego żądania http pod warunkiem, że nie został rzucony wyjątek lub transakcja została na którymś etapie odrzucona (pojedyncze żądanie http to jedna transakcja, która albo się wykona w całości, albo się nie wykona w ogóle). Odrzucanie, commitowanie transakcji oraz zarządzaniem cyklem życia DBContext zajmuje się u mnie IUnitOfWork, który jest wstrzykiwany razem z wymaganymi repozytoriami do warstwy serwisów aplikacji.

2
micc napisał(a):

Nie widzę różnicy pomiędzy zapisem GetCustomerByCityName("Warszawa") a zapytaniem FindWhere(a=>a.CityName.Equals("Warszawa")).

A między FindWhere(a=>a.Addresses.Cities.Where(c => c.Name.Equals("Warszawa")) też nie?
W ten sposób, w warstwie logiki biznesowej zajmujesz się konstrukcją zapytań o dane. Te wszystkie lambdy to są właśnie zapytania o dane, kompletnie nieistotne z punktu widzenia logiki biznesowej.

Zapytania nie buduję w warstwie biznesowej, ponieważ LINQ jest częścią wykorzystywanego języka programowania, a nie warstwy dostępu do danych (jako parametry do metod repozytoriów używane są tylko te wyrażenia, które są zgodne z LINQ to Objects).

LINQ, jak sama nazwa wskazuje, to język zapytań. Używając go gdziekolwiek, budujesz zapytanie do źródła danych.

Zawsze mogę przejść przykładowo z EF na nHibernate i wyłuskać wymagane search criteria z wyrażenia LINQ.

Tylko kto używa LINQ w NHibernate?
Właśnie to jest ten problem - jakbyś miał prawdziwe repozytorium, to byś mógł faktycznie efektywnie podmienić źródło danych. Ty masz repozytorium ściśle związane z LINQ, więc możesz użyć tylko tego, co jest z tą technologią kompatybilne.
To właściwie dość dziwnie wygląda, gdy tyle piszesz o abstrakcji, elastyczności i SoC, jednocześnie ściśle wiążąc się z jedną konkretną biblioteką ORM.

Mówiąc o "rzeczywistej kolekcji" miałem na myśli operowanie w warstwie biznesnowej na IEnumerable, a nie IQueryable.

No to ja dokładnie tak robię, i nie mam repozytorium. (Zresztą, IQueryable w kontekście dostępu do danych w ogóle nie używam.)

http://www.ben-morris.com/why-the-generic-repository-is-just-a-lazy-anti-pattern
http://www.sapiensworks.com/blog/post/2012/03/05/The-Generic-Repository-Is-An-Anti-Pattern.aspx
http://ayende.com/blog/3955/repository-is-the-new-singleton

0

Masz rację, że wiążę się w ten sposób z technologią LINQ ale jest ona jak by nie patrzeć częścią platformy .NET, która sporo ułatwia.
Oczywiście łatwiej jest przejść na inny ORM tworząc same konkretne repozytoria, których argumenty to typy proste. Wymaga to również więcej czasu od programisty i prowadzi do olbrzymiego rozrastania się repozytoriów.
Istnieją jednak sposoby rozkładu drzewa wyrażenia LINQ tak, aby wyłuskać odpowiednie argumenty. Gdybym miał przejść na inny ORM, to osobiście wolałbym opracować i dodać do implementacji repozytorium mechanizm rozkładający drzewo LINQ i przekładający je do zapytania docelowego ORM, zamiast podmieniać implementację każdej z metod osobno.
Trudno ocenić czy byłoby to mniej/bardziej udane pod względem efektywności, niż gdybym miał operować w pełni na konkretnych repozytoriach, ale cóż - lenistwo.

0

Nikt nie pomyślał o tym, że dzięki temu mamy praktycznie gotowe API do wystawienia na zewnątrz?

0
emfałsi napisał(a):

Nikt nie pomyślał o tym, że dzięki temu mamy praktycznie gotowe API do wystawienia na zewnątrz?

W sensie wystawić na zewnątrz API dostępu do bazy? A w jakim celu?

0

@somekind

somekind napisał(a):
emfałsi napisał(a):

Nikt nie pomyślał o tym, że dzięki temu mamy praktycznie gotowe API do wystawienia na zewnątrz?

W sensie wystawić na zewnątrz API dostępu do bazy? A w jakim celu?

Nie koniecznie musi to być stricte dostęp do bazy przecież ? A w jakim celu swoje API wystawia Facebook czy Filmweb ? Czy nie po to, żeby korzystać z dobrodziejstw ich bazy danych ?

0

Repozytoria dają dostęp tylko i wyłącznie do bazy, normalnie API zapewniają raczej coś więcej pod względem funkcjonalnym, tj. jakąś tam logikę.
Po drugie "generyczne repozytorum" tak tu lansowane wymaga do działania klas zdefiniowanych w aplikacji, więc jest to raczej mało wygodne dla użytkownika tego API.
Po trzecie publiczne API ma sens w przypadku tego typu publicznych portali, ale w przypadku intranetowego softu raczej żaden.

0

Do wystawienia na zewnątrz to raczej bardziej pasowałoby udostępnienie warstwy serwisów aplikacji (całości, bądź podzbioru). Wtedy możemy wybrane metody serwisów oznaczyć jako kontrakty i wystawić na zewn. np. poprzez WCF.

0

Nie mówiłem o generycznym repozytorium o którym wspominasz.
Chodziło mi raczej o to, że mając połączenie App <-> WCF/WS, możesz tego WCFa/WSa lub jego część wystawić łatwo w formie API.

0
emfałsi napisał(a):

Nie mówiłem o generycznym repozytorium o którym wspominasz.
Chodziło mi raczej o to, że mając połączenie App <-> WCF/WS, możesz tego WCFa/WSa lub jego część wystawić łatwo w formie API.

WCF to raczej serwis biznesowy niż repozytorium.

0
somekind napisał(a):
siararadek napisał(a):

To bezpośrednio na DataContext operujesz?

W przypadku Entity Frameworka owszem, w przypadku NHibernate korzystam z odpowiednika czyli Session.

Jak wyglądają u Ciebie wtedy testy jednostkowe? We własnym projekcie zastosowałem te Repository / UnitOfWork itd ale nie podoba mi się struktura tego, nadbudowa czegoś co praktycznie istnieje. Sugerowałem się wieloma tutorialami z internetu, czy nawet książką Pro ASP.NET MVC 3 (fragment z niej):

[TestMethod] 
public void Correct_Total_Reduction_Amount() { 

Product[] products = new Product[] { 
new Product() { Name = "Kayak", Price = 275M}, 
new Product() { Name = "Lifejacket", Price = 48.95M}, 
new Product() { Name = "Soccer ball", Price = 19.50M}, 
new Product() { Name = "Stadium", Price = 79500M} 
}; 

Mock<IProductRepository> mock = new Mock<IProductRepository>(); 
mock.Setup(m => m.GetProducts()).Returns(products); 
decimal reductionAmount = 10; 
decimal initialTotal = products.Sum(p => p.Price); 
MyPriceReducer target = new MyPriceReducer(mock.Object); 

target.ReducePrices(reductionAmount); 

Assert.AreEqual(products.Sum(p => p.Price), (initialTotal - (products.Count() * reductionAmount))); 
}

Możesz podać jakiś fragment testów, gdy korzystamy bezpośrednio z DbContext - robisz fakowy context, czy jak?

0

Jest to do zrobienia. W przypadku nHibernate możemy bezpośrednio mockować ISession. W przypadku EF można zrobić coś takiego:

    public interface IDbContext
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
        ...
        inne potrzebne nam metody
    }

   public class MojDbContext : System.Data.Entity.DbContext , IDbContext
    {
        public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
        {
            return base.Set<TEntity>();
        }
        ....
    }

W naszej aplikacji jako IDbContext wstrzykiwane jest MojDbContext.
W testach jednostkowych mockujemy sobie wtedy IDbContext, który jest fasadą na DbContext.

0

Co jest złego w Repository?

Zaczyna się od CustomerRepository z metodami GetCustomerById, GetCustomerByName, GetCustomerByNIP. Wszystko jest fajnie. Potem okazuje się, że trzeba pobrać klienta razem z fakturami, więc ktoś zmienia zawartość tych metod tak, że zachłannie pobierane są faktury. Oczywiście nigdzie w kodzie tego nie widać, do repozytorium nikt inny nie zagląda (bo po co?), ale ludzie dziwią się, że w wielu miejscach aplikacja zwolniła... Na szczęście znalezienie przyczyny nie trwa długo, i trzeba to naprawić. Więc co się robi? Nowe metody o więcej mówiących nazwach: GetCustomerWithInvoicesById, GetCustomerWithInvoicesByName, GetCustomerWithInvoicesByNIP. I jest wszystko dobrze!
Przynajmniej na razie, bo z czasem pojawia się potrzeba pobrania klientów wg adresu wraz z jego pracownikami, ale bez faktur: GetCustomersWithEmployeesButWithoutInvoicesByAddress, a potem potrzeba pobrania wszystkich klientów, z fakturami, bez pracowników, z ulubionymi filmami, bez kochanek wg grupy krwi żony i imienia kota córki prezesa: GetCustomersWithInvoicesWithoutEmployeesWithFavouriteMoviesWithoutMistressesByWifeBloodTypeAndChairmanDaughterCatName.
Im bardziej skomplikowana domena tym więcej potrzeb kombinacji warunków i wyciągania powiązanych encji, tym więcej metod w klasie Repozytorium. Kończymy z klasą z milionem metod... i to ma być dobre?

To w takim razie jak najlepiej sobie radzić z powtarzającymi się zapytaniami? Mam np pobieranie wszystkich faktur z pozycjami, używam tego w 3 miejscach. Jak mam repozytorium to potem w razie zmian wystarczy że to zmienię w jednym miejscu, a jak chce zmienić też nazwy to łatwo wyszukam te 3 i je zmienię. Według Ciebie nie powinno się tego używać, więc jakie rozwiązanie proponujesz w zamian, żeby zapytania były spójne i możliwe do wykorzystania w kilku miejscach?

1
  1. Jak masz DDD to miej repozytorium. Jak nie masz DDD, to nie udawaj, że je masz poprzez nazwanie DALa tym słowem. Jest wiele innych słów: DataSource, DataReader, Provider, i pewnie wiele innych, którymi możesz takie klasy nazwać.
  2. Fragment, który zacytowałeś dotyczy sytuacji, w której wydaje nam się, że mamy takie same zapytania, ale tak naprawdę nie są takie same - bo za każdym razem tak naprawdę mamy albo inną projekcję albo inną selekcję. Czyli albo potrzebujemy innych danych, albo inaczej je filtrujemy. Ergo - to nie jest powtarzanie się zapytań. I ten problem można rozwiązać na wiele sposobów, także przenosząc taką logikę do klasy wykonującej zapytania albo np. do klasy tworzącej obiekty zapytań (wyrażenia LINQ albo ICriteria z NH).
  3. Tylko dla mnie to taki trochę sztuczny problem. Nie przypominam sobie, abym kiedykolwiek widział, żeby to samo zapytanie było potrzebne w dwóch miejscach. Czemu? Patrz pkt 2.
0

Repozytoria są dobre w przypadku gdy nie posługujemy się obiektami POJO tylko tworzymy modele dziedziny, czyli po prostu korzystamy z DDD.

1
somekind napisał(a):

Ale ukrywanie w repozytorium ORMa jest tylko dokładaniem zbędnej warstwy, która nie wnosi chyba żadnej wartości dodanej.

Oczywiście to zagadnienie jest celem wielu dyskusji, ja należę chyba do tej drugiej strony barykady. Spróbuj nie użyć repozytorium i chociażby podmieć ORM (EF > NHibernate). Z pewnością konieczność zmian w logice będzie świetną zabawą. To samo tyczy się testów jednostkowych- jeśli będziesz musiał dostarczyć do testsowanego modułu jakąś testową wersję ORM'a to nie są to już testy jednostkowe. Mając abstrakcje w postaci repozytorium to nie jest problemem ponieważ łatwo to zmockować bez bycia przywiązanym do konkretnej technologii. No i właśnie- nieważne w jak ładne słowa by się tego nie ubrało to ORM nadal jest konkretną technologią z którą nasza logika bizensowa jest związana. Powinniśmy programować do interefejsów a nie konkretnych implementacji.

Oczywiście zawsze należy starać się być pragmatycznym więc powyższe niekoniecznie tyczy się bardzo małych programów.

0
emfałsi napisał(a):

@somekind

somekind napisał(a):
emfałsi napisał(a):

Nikt nie pomyślał o tym, że dzięki temu mamy praktycznie gotowe API do wystawienia na zewnątrz?

W sensie wystawić na zewnątrz API dostępu do bazy? A w jakim celu?

Nie koniecznie musi to być stricte dostęp do bazy przecież ? A w jakim celu swoje API wystawia Facebook czy Filmweb ? Czy nie po to, żeby korzystać z dobrodziejstw ich bazy danych ?

Przepraszam, że broniłem wzorca repozytorium, przez 7 lat kariery nie widziałem w żadnej firmie dobrej implementacji tego, w pewnym momencie robił się tam zawsze taki śmietnik, że nie da się tego utrzymywać (w jednych firmach ten moment następował wcześniej, w innych później), dużo lepszym podejściem w mojej opinii jest CQRS! :)

0
somekind napisał(a):

GetCustomersWithEmployeesButWithoutInvoicesByAddress

Ogólnie to robisz tutaj dobrą robotę ale to mnie rozbawiło, GetCustomersWithEmployeesButWithoutInvoicesByAddress możesz mi sprostować o co ci chodzi ?? czy ty swoje agregaty pobierasz takimi metodami jeśli tak to jako one wyglądają ?.

Jest w tych czasach jakikolwiek sens używać repo bez DDD?

1
Krwawy Orzeł napisał(a):

Ogólnie to robisz tutaj dobrą robotę ale to mnie rozbawiło, GetCustomersWithEmployeesButWithoutInvoicesByAddress możesz mi sprostować o co ci chodzi ?? czy ty swoje agregaty pobierasz takimi metodami jeśli tak to jako one wyglądają ?.

Tak, ja. :D

Moim zdaniem wystarczająco dokładnie opisałem proces patologizacji repozytorium w swoim poście. Takie są po prostu efekty stosowania pseudorepozytoriów (czyli DAO nazwanego repozytorium) do CRUD i pchania "encji" (w sensie ORM, nie DDD) od bazy do widoku.

Jest w tych czasach jakikolwiek sens używać repo bez DDD?

To pytanie nawet nie ma sensu, bo repozytorium nie istnieje poza DDD.

0

https://www.infoq.com/presentations/ddd-net-2
Od około 45 minuty gość pokazuje repozytoria. Opis twierdzi, że gość jest principalem w Microsofcie.

0

Co on pierniczy? w 42 minucie mówi że repo w wersji Fowlera miało na celu stworzenie abstrakcji nad mechanizmem persistence a Evansowi nie chodziło w ogóle o wydzielenie abstrakcji nad persistence a wydzielenie abstrakcji nad kolekcją obiektów a abstrakcja nad persistence to tylko efekt uboczny :O. To ciekawe zwłaszcza, że książka Evansa jak i Vernona odnosi się nie jednokrotnie do książki Fowlera a samo DM w kontekście DDD wymaga wydzielenia owej abstrakcji nad persistence. A co do tej jego implementacji "DDD Repository" to ja nie będę tego komentował nawet.

1 użytkowników online, w tym zalogowanych: 0, gości: 1