Wzorzec repozytorium

0
Afish napisał(a):

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

Czym jest ? ;)

1

Ciekawa dyskusja.

Właśnie w moim projekcie wywalam Repository z nazw i wstawiam Dao :P
Bo nie robią nic tylko zwracają dane z "bazy"(webApi) i mapują na moje DTO.

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?

A jak masz listę faktur do wyświetlenia to jak to robisz? Założmy, że masz jakiegoś ORM i wtedy w tym kontrolerze bezpośrednio piszesz zapytanie? Jeśli nie to jak to u Ciebie wygląda?

0

Żadnych ORMów ani zapytań w kontrolerach. Na ORMie operuję w jakichś klasach z warstwy aplikacji. Jeśli mowa o odczycie danych na potrzeby GUI, to wręcz taką klasę nazywam viewmodelproviderem.

0

@somekind: Mam kilka pytań.

  1. Jak implementujesz operacje CRUDowe, jeśli nie masz repozytoriów, a kontrolery nie wiedzą nic o ORMie? Tworzysz klasy typu AddXService, UpdateXService itd., a w kontrolerach odwołujesz się do ich interfejsów tych klas?
  2. W jaki sposób budujesz zapytania i je egzekwujesz?
  3. Gdzie mapujesz encje na DTO?
  4. Gdzie jest walidacja danych wejściowych? Usługi zwracają jakieś enumy określające status operacji?

A, i jeszcze jedno. Nie wiem, czy to Ty jesteś autorem tego artykułu (2016 rok): http://commitandrun.pl/2016/05/11/Repozytorium_najbardziej_niepotrzebny_wzorzec_projektowy/, ale tekst w sekcji 'Rozrost Repozytoriów' jest skopiowany z Twojego postu na początku tego wątku (2013 rok). ;)

0
Trzeźwy Kura napisał(a):
  1. Jak implementujesz operacje CRUDowe, jeśli nie masz repozytoriów, a kontrolery nie wiedzą nic o ORMie? Tworzysz klasy typu AddXService, UpdateXService itd., a w kontrolerach odwołujesz się do ich interfejsów tych klas?
  2. W jaki sposób budujesz zapytania i je egzekwujesz?
  3. Gdzie mapujesz encje na DTO?
  4. Gdzie jest walidacja danych wejściowych? Usługi zwracają jakieś enumy określające status operacji?

Zapytanie oraz mapowanie generuje algorytm. Ja tylko podaje do niego początkowe wartości jak restrykcja filtra i jego reguły oraz listę pól, które chcę pobrać. Algorytm filtrowania mam zamodelowany jako Domain Model, mapowanie oraz ORM chowam za abstrakcją DataQueryProvider. ReaderDtoApplicationService woła DataQueryProvider oraz Filtr z Domeny, który konfiguruje kryteria filtrowania i podaje je do Provaidera. StorageOperation implementuje w infrastrukturze a UnitOfWork steruje Contener IOC.

A algorytm mapowania, jak i wybierania zapytania SQL bazuje na zbieżności nazw pól pomiędzy PersistenceMode a DataReslut dla Generycznego DTO, które wzbogacam o metadane dla widoku.

Mam nadzieję, że pomogłem...

0
Trzeźwy Kura napisał(a):

@somekind: Mam kilka pytań.

  1. Jak implementujesz operacje CRUDowe, jeśli nie masz repozytoriów, a kontrolery nie wiedzą nic o ORMie? Tworzysz klasy typu AddXService, UpdateXService itd., a w kontrolerach odwołujesz się do ich interfejsów tych klas?

Staram się raczej nie używać sufiksu "Service". Wiele zależy od konkretnego przypadku, czasem np. Add/Update/Delete warto trzymać razem. No i nie używam interfejsów, bo one zwyczajnie nie mają sensu we własnym kodzie.

  1. W jaki sposób budujesz zapytania i je egzekwujesz?

Korzystając z API dostarczanego przez ORM. Nie wiem czy o to Ci chodziło w pytaniu, bo jest zbyt ogólne jak dla mnie.

  1. Gdzie mapujesz encje na DTO?

Jak najbliżej bazy danych. Przy wykorzystaniu z ORMa jest to miejsce wykonywania projekcji, czyli zazwyczaj metoda Select.

  1. Gdzie jest walidacja danych wejściowych? Usługi zwracają jakieś enumy określające status operacji?

Różnie z tym jest, zazwyczaj wygląda to tak, że warstwa logiki aplikacji/biznesowej sprawdza sensowność podanych argumentów i rzuca wyjątki, gdy coś jest nie tak. Dokładniejsza walidacja wejścia odbywa się w warstwie prezentacji, gdzie sprawdzane są wszystkie reguły i do użytkownika zwracana jest lista komunikatów/kodów błędów.

A, i jeszcze jedno. Nie wiem, czy to Ty jesteś autorem tego artykułu (2016 rok): http://commitandrun.pl/2016/05/11/Repozytorium_najbardziej_niepotrzebny_wzorzec_projektowy/, ale tekst w sekcji 'Rozrost Repozytoriów' jest skopiowany z Twojego postu na początku tego wątku (2013 rok). ;)

Wielkie umysły myślą podobnie.

0

@somekind: Dziękuję, zaczyna mieć dla mnie sens to, co mówisz, ale mam jeszcze kilka pytań. Z tego, co zrozumiałem, w kontrolerach wykorzystuję usługi, które korzystają z pewnych data providerów, a gdy coś pójdzie nie tak, rzucany jest wyjątek, który przechwytuje kontroler, a następnie zwraca odpowiedni rezultat. Tylko zastanawia mnie, w czym taki data provider jest lepszy od "typowego" repozytorium (poza tym, że nie udaje czegoś, czym nie jest). W końcu wraz z rozwojem aplikacji metod będzie przybywać, więc trzeba te providery jakoś sensownie podzielić na mniejsze klasy. W pytaniu drugim chodziło mi o to, jak to zrobić, by było dobrze ;) Albo może tworzyć jakieś obiekty zapytań typu GetXByYQuery i przekazywać je do metod providerów? Btw świetnego masz tego bloga. ;)

5

Przykładowy pseudokod do dalszych rozważań:

public class CustomerController
{
	private CustomerViewModelProvider provider;
	private CustomerStore store;
	
	// odpowiedni konstruktor

	public DataPage<CustomerViewModel> Index(DataPageRequest<CustomerViewModel> request)
	{			
		return provider.GetDataPage(request);
	}
		
	public CustomerDetailsViewModel Details(long customerId)
	{
		return provider.Get(customerId);
	}
	
	public CreationResult Create(CustomerCreateModel customer)
	{
		return store.Create(customer);
	}
	
	public DeleteResult Delete(long id)
	{
		return store.Delete(id);
	}	
	
	public OperationResult WinThePrize(PrizeRequest request) // jest po 17 w piątek, idę na piwo i nie mam sensownego pomysłu
	{
		return customerService.Win(request);
	}
}
public class CustomerService
{
	public Win(PrizeRequest request)
	{
		// session to obiekt jakiegoś ORMa, niekoniecznie istniejącego
		var customer = session.Get<Customer>(request.CustomerId);
		var products = session.GetAll<Product>(request.ProductCategory, request.MinimumPrice);
		
		var magicService = new DrawingService(products);		
		customer.Prize = magicService.GetPrize();
		
		session.CommitAndFlush();
	}
}

DataPageRequest - generyczna klasa zawierająca takie pola jak numer strony do pobrania, liczba pozycji na stronę i warunki filtrowania (np. jako słownik nazwa właściwości: wartość filtra) oraz warunki sortowania (np. jako słownik nazw właściwości i kierunku sortowania).
DataResponse - lista viewmodeli zmapowanych z bazy, numer strony, wielkość strony, liczba wszystkich stron.

Trzeźwy Kura napisał(a):

@somekind: Dziękuję, zaczyna mieć dla mnie sens to, co mówisz, ale mam jeszcze kilka pytań. Z tego, co zrozumiałem, w kontrolerach wykorzystuję usługi, które korzystają z pewnych data providerów, a gdy coś pójdzie nie tak, rzucany jest wyjątek, który przechwytuje kontroler, a następnie zwraca odpowiedni rezultat.

No ja bym raczej widział, że do pewnych rzeczy są serwisy, do pewnych providery, do jeszcze innych inne klasy. Nie ma po co tworzyć warstw wrapperów.
A wyjątki przechwytuje filtr albo handler, nie ma po co dręczyć tym kontrolery.

Tylko zastanawia mnie, w czym taki data provider jest lepszy od "typowego" repozytorium (poza tym, że nie udaje czegoś, czym nie jest). W końcu wraz z rozwojem aplikacji metod będzie przybywać, więc trzeba te providery jakoś sensownie podzielić na mniejsze klasy.

Ale kiedy niby będą przybywały metody?
Logika biznesowa nie korzysta z providerów, jedyne przypadki użycia mając wpływ na ich liczność, to nowy ekran w GUI.
Jeśli napiszesz ViewModelProvider niegenerycznie, to będą przybywały nowe klasy providerów.
Jeśli napiszesz generycznie, to nie będzie przybywać ani metod ani klas providerów.

W pytaniu drugim chodziło mi o to, jak to zrobić, by było dobrze ;) Albo może tworzyć jakieś obiekty zapytań typu GetXByYQuery i przekazywać je do metod providerów?

Też można - jeśli oczywiście masz jakieś "sztywne" ekrany w aplikacji, które nie pozwalają użytkownikowi na przeszukiwanie samodzielnie.

0

Nawiązując do wypowiedzi @somekind, chciałbym dodać, że...

Aby to wszystko było zgodne z SOC, powinieneś trzymać ViewModele w warstwie pośredniej pomiędzy logiką "biznesową, aplikacji" a warstwą prezentacji. Może to trochę przypominać pojęcie kontraktów z SOA albo prościej, możesz na to patrzeć jak na warstwę Common. Innym wyjściem jest zrobienie oddzielnej warstwy z ReaderViewModel (Osobiście mi się to nie podoba). Mógł byś również zdefiniować interface dla Readera w warstwie prezentacji ale to bez sensu.

Dalej jeszcze o SOC, jeśli w warstwie prezentacji nie renderujesz HTML i chcesz zwrócić zwykłe DTO - Resources (mniejsza o to jak to zwał) możesz sobie zrobić w warstwie prezentacji Dekorator na Readera, który doda jakieś dodatkowe elementy jak np. Linki na styl HATEOAS.

Chciałbym również zacząć pisać własnego bloga, w którym będę między innymi opisywał jak zrobić generyczną bibliotekę dla operacji CRUD. Chcielibyście coś takiego czytać...?

0
._. napisał(a):

Aby to wszystko było zgodne z SOC, powinieneś trzymać ViewModele w warstwie pośredniej pomiędzy logiką "biznesową, aplikacji" a warstwą prezentacji.

Można tak robić, i bardzo często ma to sens, ale nie trzeba. Kontrolery mogą np. składać viewmodele z danych otrzymanych z warstwy aplikacji.

0

Można tak robić, i bardzo często ma to sens, ale nie trzeba. Kontrolery mogą np. składać viewmodele z danych otrzymanych z warstwy aplikacji.

Zgadza się, ja tylko wskazuje na unikanie trzymania ViewModelów w warstwie aplikacji, wielu ludzi wpada w pułapkę albo tutaj, albo tam ;). A z tymi dekoratorami czy fasadą to też zależy. Moje zdanie jest takie, że jeśli chcesz dodać jakiegoś stringa na potrzeby View itp, to zrób to w kontrolerze. Jeśli do kontrolera wstrzykujesz kilka serwisów i do tego jeszcze jakiegoś utilsa i mieszasz tym wszystkim w kontrolerze, no to nie bardzo mi się to podoba. Z tego, co zauważyłem to ludzie też nadużywają AutoMappera gdzie większość ViewModeli zwrotnych można ustawiać przez konstruktor zamiast publiczne propertisy.

0

A jak korzystamy z jakiegoś paginatora do danych w datagridach to to wywołanie tego paginatora może być w repozytorium czy to powinna być osobna usługa, która w parametrze przyjmie repozytorium?

0

A po co w ogóle do tego repozytorium, skoro to nie jest logika biznesowa tylko prezentacja danych? Po co wprowadzać jakieś warstwy po drodze?

0

Ale żeby paginator mógł zadziałać to najpierw musi mieć te dane z repozytorium. Tutaj chodzi tylko o podział tych danych, to nie jest prezentacja.

1

Paginacja to nie jest logika biznesowa. Zrób sobie jakiś serwis który przyjmie jako parametr detale tej paginacji i zwróć dane tym serwisem. Tutaj nie potrzeba repozytorium.

0
Krzywy Szczur napisał(a):

Ale żeby paginator mógł zadziałać to najpierw musi mieć te dane z repozytorium.

Kto tak powiedział? Repozytorium niczego nie przechowuje, więc nie może być źródłem niczego.

Tutaj chodzi tylko o podział tych danych, to nie jest prezentacja.

Jeśli coś wyświetlamy w GUI aplikacji to to jest prezentacja. A stronicowanie nie jest potrzebne w innych sytuacjach niż wyświetlanie danych użytkownikowi.

0

To nie są, żadne dane tylko metadane. Repository ma zwracać obiekty domenowe. Paginacja powinna być zainicjowana tylko raz, a potem tylko przetwarzać metadane, które jej podajesz przez Dto albo ViewModel. Paginacja w ogóle nie powinna być świadoma istnienia źródła "danych".

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