Cześć,
poczytałem trochę wątków na forum np. ten i mam spory niedosyt. Wiem, że łatwo jest obśmiać takie podejście, ale nie czuję w każdym przypadku takiego odstręczenia od EF + repository&UoW pattern, jak to się każdy naśmiewa z wyższością dla takich rozwiązań. Dlatego proszę o grzeczne wytłumaczenie dziur w mojej logice.
Załóżmy że mamy WebAPI + EF na już istniejący od dawna kawałek bazy. Nie ma mowy o żadnym DDD. Operacje to nie tylko CRUD na tabelkach (chociaż też), ale dużo bardziej skomplikowane zapytania i również miejscami złożony zapis.
W akcji kontrolera chcemy mieć jak najmniej kodu, to co można jak np. walidacja realizujemy poprzez middlewary. Nie ma tu też miejsca na logikę biznesową, więc większość wydzieli service, managera czy jak kto nazywa ten znienawidzony i prawie zawsze bezsensowny twór.
Tutaj moglibyśmy wstrzyknąć DbContext i poisać query, modyfikować obiekty i na koniec dać _context.Save(). Widzę tutaj jednak zastosowanie dla repozytorium, ponieważ zamykamy te czynności w kolejnej warstwie abstrakcji, którą się łatwiej testuje. Dla czystości kodu z repozytorium umawiamy się, że nie zwracamy nigdy IQueryable tylko IEnumerable. Mamy więc w zamkniętym pudełku chociażby zapytania i nie martwimy się, że przyjdzie stażysta i w warstwie pośredniej po prostu zacznie dopisywać kolejne warunki do query, a widziałem takie bagno wielokrotnie, że ktoś w samym kontrolerze bezpośrednio pisał i modyfikował np. query _context.Product.Where..... Wg mnie dobrze jest zamknąć zapytania i na zewnątrz jedynie IEnumerable wystawić, a samego IDbContext nie wstrzykiwać nawet do kontrolera czy managera/service'u. Jako gratis możemy zrobić generyczną klasę BaseRepository do ogarnięcia prostego CRUDa, który też się zdarzy i nie musimy pisać wiele kodu powtarzając się.
Zacznijmy od tego, że samo EF jest UoW i wydaje się, że nie ma po co opakowywać EF jeszcze Uow. Stosując jednak repository pattern rujnujemy całkiem działający w EF UoW i musimy napisać swój własny UoW, by korzystając z różnych metod w różnych repozytoriach zapewnić transakcyjność i na koniec mieć możliwość użycia jednokrotnego _uow.Save(); Tutaj czuję oczywiście ten zgrzyt, że sami niszczymy abstrakcję, którą zaraz sami produkujemy.
Alternatywą oczywiście przy takim systemie, który nie jest samym CRUDem (ale też nim w pewnym zakresie jest) wydaje się CQRS (nie mieszajmy w to jednak DDD i Event Sourcingu, który w zaistniałym przykładowym systemie jest nie do przeforsowania). Bardziej skomplikowane query i commandy mamy ładnie wydzielone w osobnych klasach (zamiast repozytoriów z długą ilością linii kodu mamy od cholery klas pogrupowane w katalogach - jednego i drugiego nie lubię). DbContext wstrzykujemy tylko w query i commandy co jest eleganckim rozwiązaniem, by ktoś nietestowalnego łatwo query nie budował w akcji kontrolera.
Wg mnie stworzenie generycznych handlerów do obsługi również prostych operacji CRUDowych jest dużo bardziej skomplikowane niż generyczne BaseRepository, albo tworzymy masę takiego samego kodu i klas do obsługi głupiego FindById, GetAll, Create, itd. No chyba że wstrzykniemy jednak do tak prostych operacji IDbContext to kontrolera, co prowadzi do punktu pierwszego i daje stażyście łatwy dostęp by na kolanie użyć w akcji contextu EF.
Ogarnięcie transakcyjności też jest problemem i zazwyczaj widziałem po prostu bardzo długie comandy, które aż się proszą o podzielenie ich na logiczne mniejsze klocki, co wychodzi naturalnie przy repository pattern. Widywałem też wstrzyknięcie IDbContext do kontrolera/managera by na koniec dać _contex.Save(), ale o tym dlaczego tego nie lubię już pisałem kilkukrotnie.
Owszem sam CQRS często przy użyciu MediatR daje nam kilka innych możliwości, których nawet nie musimy potrzebować jak np. pipeline'y, gdyż często łatwiej można rozwiązać autoryzację i logowanie poprzez middleware'y/filtry w ASP, a notifications w ogóle możemy też nie potrzebować. Nie pomaga też fakt, że jak ktoś tłumaczy właśnie CQRS to robi to na przykładzie CRUDa albo czegoś równie prostego.
Naprawdę ciężko mi znaleźć złoty środek dla takiej aplikacji, która realizuje skomplikowane procesy, wyliczenia, raporty i jednocześnie ma dawać dostęp do prostego CRUDa i to wszystko bez możliwość DDD, bo relacyjna baza już stoi pełna ciągle używana przez inne części systemu i nie wolno ci stworzyć nowej. Za wszelkie cenne wskazówki i polemikę będę wdzięczny. Za krótkie rzucenie ogólnikowych haseł bez wyjaśnienia nie bardzo.
Tematu robienia abstrakcji, bo kiedyś się podmieni bazę, ORMa na coś innego nawet nie biorę pod uwagę bo jeszcze nigdy w mojej karierze tego nie widziałem, choć często takie podejście z automatu daje fajne pole do manewrów z mockowaniem przy testach.