Ile method powinno mieć repozytorium? Zazwyczaj takie crudowe repo ma przynajmniej 4 metody typu getById, add, delete itd. Tylko jaki to ma sens i czy to czasem nie łamie single responsibility principle? Załóżmy, że mam jakiś command, np. CreateTaskCommand, CommandHandler przyjmuje tego commanda, następuje walidacja i jeśli jest pomyślna, to task jest zapisywany do repo i teraz pytanie po co taki handler ma mieć wstrzyknięte wielkie repo z 5 innymi niepotrzebnymi metodami jak potrzebuje tylko metody add? W myśl CQRS można podzielić repo na query (readAll, readBy...) i command (add, update delete), ale nadal wtedy mamy spore klasy.
Liczba method w repozytorum, CRUD i SRP
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Silesia/Marki
- Postów: 5549
Ja od dawna mówię Jedna klasa - jedna metoda publiczna. Tylko tak można nie złamać SRP.
Nawet kiedyś udało mi się taki mikroserwice napisać. Co prawda nie przedłużyli wtedy ze mną umowy, ale warto było
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Wrocław
Najlepiej jeśli nie ma repozytorium, wtedy nie da się złamać żadnych reguł.
SRP mówi o tym, że klasa powinna mieć tylko jeden powód do zmiany. Repozytorium to kolekcja obiektów danego typu, która z wymienionych metod kolekcji łamie zatem SRP i w jaki sposób?
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Wrocław
Nie jestem przeciwnikiem repozytoriów tylko ich błędnego używania jako coś, co w ogóle ma związek z CRUDem, czy tym bardziej z bazą danych.
Jak napisałem wcześniej (nie wiem czy zauważyłeś w edycji posta), że kolekcja ma metody do pobierania, usuwania i dodawania elementów moim zdaniem nie łamie SRP. Moim zdaniem nie da się inaczej zaimplementować kolekcji. A repozytorium jest kolekcją.
A jak robisz CRUDa, to po prostu pisz/czytaj z bazy czymś, co do tego służy, np. ORMem.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 58
@somekind: No dobra, rozumiem już różnice między repo a dao (chociaż przecież na repo też można przeprowadząć operacje crudowe), ale nadal moje pytnie pozostanie aktualne. Dlaczego jakiś servis/handler jak zwał tak zwał, ma mieć dostęp do całego api dao a nie tylko do określonej metod np. add? Byłoby słabo gdyby ktoś np. w tym handlerze wyczyścił tą listę albo przeprowadził jakaś inną niepożądana operację. Kolejna sprawa, to wyobraźmy sobie, że chcemy wystawić jakieś restowe api, ale tylko takie, które umożliwia odczyt. Jak to zrobić jak jest jeden controller np. TaskController z wstrzykniętym wielkim crudowym serwisem, który tańczy i śpiewa? Kolejna sprawa. Ze wstrzykiwaniem zależności to jeszcze pół biedy, ale wyobraż sobie, że chcesz pobrać coś z bazy. Tworzysz new CrudService i musisz podać dao, validatory do innych method, które przecież siedzą w tym wielkim serwisie i potem dopiero getTaskById a chciałeś tylko pobrać jeden task bo id.
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Silesia/Marki
- Postów: 5549
Edelner napisał(a):
Dlaczego jakiś servis/handler jak zwał tak zwał, ma mieć dostęp do całego api dao a nie tylko do określonej metod np. add?
Dobra, to teraz pora na bardziej popularną odpowiedź. Interfejs należy do klienta a nie do implementującego. Czyli jeśli serwis potrzebuje tylko metody dodawania w repo/dao to powinieneś utworzyć interfejs z tylko jedną metodą dodawania. Jak jakiś serwis potrzebuje tylko odczytu to możesz utworzyć interfejs tylko z metodami do odczytu. Potem możesz te wszystkie interfejsy sobie zaimplementować. Czy zaimplementujesz to sobie za pomocą jednego repo/dao czy za pomocą wielu to już szczegół implementacyjny. Czyli w Javie byłoby mniej więcej tak:
class SomeDao implements SomeDaoRead, SomeDaoAdd {
}
Ewentualnie można wymyślić lepsze nazwy, ale ja nigdy tego nie umiałem :(
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Wrocław
Edelner napisał(a):
@somekind: No dobra, rozumiem już różnice między repo a dao (chociaż przecież na repo też można przeprowadząć operacje crudowe), ale nadal moje pytnie pozostanie aktualne. Dlaczego jakiś servis/handler jak zwał tak zwał, ma mieć dostęp do całego api dao a nie tylko do określonej metod np. add? Byłoby słabo gdyby ktoś np. w tym handlerze wyczyścił tą listę albo przeprowadził jakaś inną niepożądana operację.
No tak, tylko w ogólnym przypadku (bo szczególny @KamilAdam pokazał wyżej) to jest nie do uniknięcia. M.in. dlatego poza kodem potrzebne są testy, które sprawdzają, czy otrzymujemy pożądane wyniki.
Kolejna sprawa, to wyobraźmy sobie, że chcemy wystawić jakieś restowe api, ale tylko takie, które umożliwia odczyt. Jak to zrobić jak jest jeden controller np. TaskController z wstrzykniętym wielkim crudowym serwisem, który tańczy i śpiewa? Kolejna sprawa. Ze wstrzykiwaniem zależności to jeszcze pół biedy, ale wyobraż sobie, że chcesz pobrać coś z bazy. Tworzysz new CrudService i musisz podać dao, validatory do innych method, które przecież siedzą w tym wielkim serwisie i potem dopiero getTaskById a chciałeś tylko pobrać jeden task bo id.
Wielki crudowy serwis wstrzyknięty do kontrolera wygląda na bardzo zły pomysł.
Ja bym użył w handlerze konkretnego use case jakiegoś ORMa, a jeśli nie mam ORMa, to jakiegoś swojego DAO tam, gdzie operuję na danych. A walidacja to coś, co odbywa się w ogóle przed wejściem do handlera.
Jeśli nie chcemy mieć mediatora i handlerów, to z kontrolera możemy wołać serwis aplikacyjny, który nadal może mieć tylko jedną metodę obsługującą dany przypadek użycia, i taki serwis w przypadku CRUDa tak samo może korzystać z ORM/DAO bezpośrednio.
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Gdańsk
- Postów: 271
KamilAdam napisał(a):
Ja od dawna mówię Jedna klasa - jedna metoda publiczna. Tylko tak można nie złamać SRP.
Czysto hipotetycznie, niestety niektórzy mają taki skill, że nawet przy takim podejściu można łamać SRP, nazywając tę jedną metodę publiczną np. CreateOrUpdateTaskOrEvenDeleteRandomlyForFun() :)
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Londyn
- Postów: 873
Otagowałeś temat DDD więc uznam że w tym kontescie szukasz odpowiedzi. Dodam, że AFAIK jest coś takiego jak Repozytorium w JPA i nie jest to to samo co repo w rozumieniu DDD. Owszem, można użyć ale rozmawiamy o wzorcu a nie o tym co sobie JPA nazwało i jak.
Ile metod? Tyle ile potrzebuje. Na pewno nie powinno mieć metod nieużywanych jeśli można tego uniknąć. "Good design is imperfect design". Jak potrzebujesz 3 metod to masz 3, jak 2 to dwie. Generalnie to dla agregatu zwykle Save i Load powinno wystarczyć. Ale jak potrzebujesz więcej to ok. I nie łamie to SRP.
Jeśli już to łamie to ISP - bo klasa która używa repo ma do dyspozycji np 5 metod, z czego potrzebuje tylko dwóch.
Co do podziału read/write:
Nawet command zwykle potrzebuje i odczytać agregat i go zapisać. Bo to agregat ma pilnować granic transakcji, nie Twój napisany z palca SQL i założenie że command ma coś zrobić bo tak. Musisz go załadować, zrobić zmianę i jeśli się udała to tylko wtedy zapisać. A jeśli nie? To nie zapisujesz. Aczkolwiek niektóre commandy są banalnie proste i to jest nad wyraz, ale skoro DDD to skomplikowana logika biznesowa i powinno mieć to sens.
Owszem, podział ma sens, zwłaszcza jeśli robisz CQRS i masz osobne bazy do odczytu i zapisu. Wtedy używasz repozytorium do zapisu i "czegoś" innego do odczytu. To coś inengo już nie pracuje na agregatach, więć nie jest repozytorium w znaczeniu z DDD. Tu mogą latać SQL, projekcje itp, co tam potrzebujesz. Zwłąszcza że powinno czytać dane z innego miejsca niż zapis. No chyba, że robisz CQRS dla ubogich, ale wtedy to inna rozmowa. I może warto zapytać po co? I czy to nie jest po prostu CQS? W każdym razie, wtedy też powinieneś/możesz użyć czegoś innego niż repozytorium do "dobrania" się do tych danych. Np... jeśli to ciągle "Daje rade" JPA reposytorium, ale w tedy to repozytoium nazywa się tak ze względu na JPA nie na Twoją architektórę.