domain driven design - gdzie w tym wszystkim logika biznesowa

1

ostatnio znalazłem przykład aplikacji, w której w miarę przynajmniej dla mnie są rozdzielone poszczególne warstwy projektu https://github.com/MarlabsInc/SocialGoal
mamy kontrolery, w których wstrzyknięte są serwisy, które to natomiast stanowią most pomiędzy kontrolerami a repozytorium.
jako, że w miarę staram się stosować zasady SOLID, to w akcjach kontrolera próbuje zawierać tylko to co konieczne i resztę oddelegowuje do serwisów, a tam wywołuje metody z repo.

ale mam w sumie taki problem, że nie za bardzo wiem, gdzie w tym wszystkim umieszczać logikę biznesową, dajmy na to zapis pliku na dysku, trzeba utworzyć ścieżkę, sprawdzić czy istnieje, jak nie to utworzyć, sprawdzić czy istnieje już plik o takiej nazwie, jak tak to zmienić, coś jeszcze jeszcze, na końcu stworzyć encję charakteryzującą ten plik i zapisać w bazie.

bez wątpienia trochę tego jest, a tym bardziej, że chce taką metodę wykorzystywać w różnych kontrolerach, to wypadałoby to jakoś wydzielić, ale nie wiem gdzie, do serwisu? jakiś bazowy kontroler?

1

O ile się nie mylę, to zgodnie z DDD logika biznesowa (np. encja charakteryzująca plik) powinna być w encjach i serwisach domenowych, a logika aplikacji (np. zapisywanie pliku, zapisywanie do bazy) w serwisach aplikacyjnych.

3

Gdy coś się nam powtarza, to naturalnie to wydzielamy, nie zależnie czy jest to logika domenowa czy nie. W tym wypadku - raczej jest to logika aplikacji.
Zapis do pliku z controlerem nie ma raczej nic wspólnego - więc osobny kontroler na to mija się z celem. Wydziel do osobnej klasy - chcesz to nazwij ją sobie serwisem ;-) albo po rpsotu wywołuj w odpowiednim serwisie tę klasę... nazwaną odpowiednio do tego co robi, np (I)RaportFileSaver. (możesz dalej dzielić, jeśli zapisujesz więcej plików, mieć jakąś klasę/interfejs bazowy - (I)FileSaver też i dziedziczyć - albo lepiej, stosować kompozycję - co sprowadzi się do zastoswania wrappera :) ) Resztę rzeczy, nie związanych z samym zapisem pliku na dysk, robisz w klasie która z niego będzie korzystać, podejrzewam że będzie to jakiś serwis w tym wypadku.

6

Pierwsza i podstawowa rzecz - nie wzoruj się na tym kodzie, on się nadaje jedynie do nauki antywzorców.

Pojedyncze kontrolery przyjmują w zależnościach kilkadziesiąt serwisów, co świadczy o potężnym naruszeniu SRP. Logika biznesowa znajduje się głównie w kontrolerach - to one wołają jakiś serwis, na podstawie wyniku podejmują decyzję, manipulują rekordem bazodanowym, a następnie wołają inną metodę jakiegoś serwisu. Tymczasem, to wszystko powinno znajdować się w serwisach aplikacyjnych, a kontrolery powinny jedynie pobrać dane od użytkownika, a następnie przekazać sterowanie do serwisu. A więc mamy tu złamane MVC, DDD i SRP.
Serwisy nie są serwisami, tylko bezsensownymi nakładkami na repozytoria, zamieniającymi jedne nazwy metod na inne.
Repozytoria, jak to zwykle bywa w dzisiejszym świecie, nie są repozytoriami tylko jakąś wariacją table data gateway czy innego DAO. Oczywiście każda tabelka ma swoje "repozytorium", co jest cechą charakterystyczną każdej, porządnie zjebanej aplikacji .NET.

Generalnie, można by te serwisy i repozytoria usunąć, kodu w kontrolerach zostało by tyle samo, aplikacja nadal nie byłaby ani MVC, ani DDD, a przynajmniej byłoby mniej plików, pseudowarstw i syfu w ogólności.

Resztę napisał @AreQrm - operacje na dysku, to nie jest logika biznesowa.

0

to bardzo bym chciał zobaczyć kod niezjebanej aplikacji .net z dobrym DDD ;)

wracając do wstrzykiwania serwisów do kontrolera, z reguły zawsze mam repozytoria powiązane z encją i wstrzykuje do kontrolera to co mi potrzeba, ale zgodnie z SRP powinienem wstrzykiwać tylko jedno repo/serwis? nigdy jeszcze tak nie miałem, żeby ta jedna wstrzyknięta zależność mi wystarczała, bo zawsze w jakiejś akcji zrobić/sprawdzić coś jeszcze trzeba a jak nie w akcji to w serwisie aplikacji, a to kolejna rzecz, która trzeba do kontrolera wstrzyknąć.

fakt faktem na początku też korzystałem z opcji, że wstrzykiwałem tylko klasę (UnitOfWork), a w niej miałem wszystkie repozytoria, czy wtedy już jest to SRP?

ogólnie przy repozytoriach generycznych uważam, że serwisy nie są głupimi nakładami, bo pozwalają odpowiednio przygotować dane do metody z repozytorium.

skoro repozytoria w ogóle nie powinny być generyczne to artykuły na asp.net mijają się z prawdą m.in właśnie o generycznym repo?

2
Złoty Kaczor napisał(a):

to bardzo bym chciał zobaczyć kod niezjebanej aplikacji .net z dobrym DDD ;)

Ja też. Tzn. może nie "bardzo", bo dla mnie DDD to głównie buzzwordowa ciekawostka, która w praktyce nie istnieje chyba nigdzie. Ale jakby ktoś kiedyś zrobił taki projekt dobrze, to przynajmniej miałbym co linkować w takich sytuacjach. :)

DDD ma sens w przypadku skomplikowanych części modelowanego biznesu. Tymczasem większość operacji w typowych aplikacjach to zwykły CRUD. Niestety, niektórzy na siłę próbują robić CRUD za pomocą DDD, i wychodzi z tego straszny przerost formy nad treścią.

wracając do wstrzykiwania serwisów do kontrolera, z reguły zawsze mam repozytoria powiązane z encją i wstrzykuje do kontrolera to co mi potrzeba, ale zgodnie z SRP powinienem wstrzykiwać tylko jedno repo/serwis? nigdy jeszcze tak nie miałem, żeby ta jedna wstrzyknięta zależność mi wystarczała, bo zawsze w jakiejś akcji zrobić/sprawdzić coś jeszcze trzeba a jak nie w akcji to w serwisie aplikacji, a to kolejna rzecz, która trzeba do kontrolera wstrzyknąć.

Trzeba wstrzykiwać tyle, ile potrzeba, ale nie sądzę, żeby kontroler potrzebował więcej niż kilku zależności. To serwisy mogą mieć więcej zależności (od innych serwisów), i logika serwisu powinna zajmować się sprawdzaniem i robieniem "czegoś jeszcze". Kontroler ma zebrać dane, wywołać serwis, odebrać wynik, i przekierować do innego widoku albo wyświetlić błąd. Tyle.

No, a przede wszystkim, jeśli wstrzykujesz do kontrolera UoW albo repozytorium, to nie masz ani DDD, ani MVC.

fakt faktem na początku też korzystałem z opcji, że wstrzykiwałem tylko klasę (UnitOfWork), a w niej miałem wszystkie repozytoria, czy wtedy już jest to SRP?

Jeśli w UoW masz repozytoria, to masz kupę trudnego w utrzymaniu kodu i złamanie OCP. (Bo dodanie nowego repozytorium powoduje zmiany w niezależnym od niego UoW.)

ogólnie przy repozytoriach generycznych uważam, że serwisy nie są głupimi nakładami, bo pozwalają odpowiednio przygotować dane do metody z repozytorium.

Ale tu nad repozytorium generycznym są repozytoria niegeneryczne, a dopiero z nich korzystają serwisy, a dane są odpowiednio przygotowywane i tak w kontrolerze.

Powinno być tak, że kontroler zbiera dane z widoku, wysyła viewmodel do serwisu, a serwis:
a) W prostym przypadku - przetwarza te dane, i aktualizuje bazę za pomocą ORMa.
b) W skomplikowanym przypadku (przy podejściu DDD) - woła serwis domenowy, który za pomocą odpowiednich metod operuje na aggregate rootach, które odpowiednio delegują te operacje do swoich encji, a następnie wywołuje repozytorium w celu zachowania zmian. W tym podejściu encje oczywiście są prawdziwymi encjami, czyli klasami z zachowaniem, a nie modelami wiersza tabeli z ORMa, zawierającymi jedynie publiczne właściwości.

skoro repozytoria w ogóle nie powinny być generyczne to artykuły na asp.net mijają się z prawdą m.in właśnie o generycznym repo?

Generyczne repozytorium to może być niepubliczna bazowa klasa abstrakcyjna dla prawdziwych repozytoriów. Ale tylko pod warunkiem, że projekt jest zgodny z DDD i występują w nim repozytoria.
Tymczasem powszechnie mianem "generycznego repozytorium" nazywa się tak naprawdę generyczny data mapper, który z repozytorium nie ma nic wspólnego.

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