Problem ze zrozumieniem architektury Spring

0

Hej, mam problem, nie potrafię do końca zrozumieć architektury tworzenia aplikacji, może ktoś tutaj będzie potrafił mi to wyjaśnić w bardziej przejrzysty sposób.
Otóż sprawa wygląda tak że nie potrafię zrozumieć klas serwisu, zaczynając od początku ja to rozumiem tak, załóżmy że użytkownik jest zalogowany i ma swoje produkty na stronie:
Klient w swoją listę produktów na stronie -> Kontroler przekazuje to polecenie od usera do serwisu w którym jest sprawdzenie czy user posiada jakieś produkty itd. -> Jeśli tak to leci zapytanie do bazy w celu pobrania produktów -> Wracamy do serwisu, tam jest to mapowane na DTO -> DTO wysyłamy do kontrolera który przekazuje to na stronę jako odpowiedz.

Moje 1 pytanie jest podstawowe, czy ja to dobrze rozumiem ?
2 pytanie Jeśli jest to formularz to na stronie powinno się umieszczać obiekt DTO, użytkownik wypełnia tak naprawdę DTO jako formularz, ten formularz dto jest wysyłany do serwisu w którym jest mapowany do encji, encja jest odpowiednio tam walidowana itd. a następnie umieszczana w bazie danych (Chyba że mapowanie powinno być jako 1 w kontrolerze ? Przed wysłaniem do serwisu )?
3 pytanie Aplikacja się rozrasta, załóżmy że nasz user ma dużo funkcji, logowanie, rejestracja, zmiana hasła, usunięcie konta, zmiana nicku, zmiana maila itd. Te wszystkie funkcje trzeba gdzieś zaimplementować, i pierwsze co mi się narzuca to UserService, ale nie wiem czy dobrze myślę bo wtedy taka jedna klasa byłaby po prostu wielkim gniotem na kilkaset lini kodu, możecie doradzić co robi się w takiej sytuacji ? Podzielić to np. na UserRegisterService, UserLoginService, UserChangePasswordService itd. i te wszystkie klasy powinny znajdować się w pakietach user->services->UserRegisterService itd.?
4 pytanie, już ostatnie :D Kwestia walidacji takich obiektów, kiedy powinna nastąpić, korzystając ze springa zazwyczaj robiłem to adnotacjami nad polami w klasie DTO, jednak wiadomo nie do wszystkiego są adnotacje a czasami np. potrzebujemy sprawdzić czy hasła są takie same, zaczynając po kolei użytkownik wpisuje formularz na stronie (Formularz jest jako DTO), ten DTO jest odbierany przez kontroler, następuje wstępna walidacja (Ilość znaków, czy nie jest puste itd.) jeśli będzie jakiś błąd to korzystam z BindingResult który ten błąd wyłapie i zwróci do użytkownika, jeśli tego "wstępnego" błędu nie ma to dto wędruje do.. ? no właśnie gdzie ? Do serwisu w którym będę miał np. UserPasswordRepeatService ? w którym sprawdzę czy hasła są takie same ? a następnie zmapuje do Encji a pozniej wykonam na niej odpowiednie operacje ?

Z góry dziękuję za każdą podpowiedz co do tego, starałem się napisać wszystko najbardziej zrozumiale jak tylko potrafiłem.

5

Pytasz o architekturę aplikacji webowej, co jest w zasadzie niezależne od Springa i różnie można do tego podejść. Nie ma czegoś takiego jak jedyna słuszna architektura - zależy pod co się optymalizujesz. Najczęściej są to jednak jakieś wariacje nt. clean architecture - polecam się zapoznać.

Odpowiadając na kolejne pytania:

  1. Mniej więcej tak, ale musisz rozumieć, po co Ci te DTO
  2. Nie wiem, co powinno, to zależy jaka masz architekturę. Zaleca się, aby kodu zależnego od weba czy baz danych nie było w tzw. kodzie domenowym. Po drugie, co do zasady, w kontrolerze nie chcesz mieć kodu domenowego, ponieważ jest on niereużywalny, nieutrzymywalny i słabo testowalny.
  3. To jest ogólna zasada w programowaniu, aby dzielić kod na mniejsze, logicznie spójne kawałki. Nie ma tutaj nic specyficznego dla Springa czy weba.
  4. Różne walidacje przeprowadza się na rożnym poziomie. Przecież na poziomie DTO nie dasz rady zwalidować np. unikalności loginu.
1

jeśli będzie jakiś błąd to korzystam z BindingResult który ten błąd wyłapie i zwróci do użytkownika, jeśli tego "wstępnego" błędu nie ma to dto wędruje do.. ? no właśnie gdzie ? Do serwisu w którym będę miał np. UserPasswordRepeatService ? w którym sprawdzę czy hasła są takie same ? a następnie zmapuje do Encji a pozniej wykonam na niej odpowiednie operacje ?

Tak tylko wydaje mi się, że chcesz dużo w kontrolerze robić a to powinno być gdzieś dalej w serwisie domenowym.

2
  1. W miarę dobrze to rozumiesz, aczkolwiek to co przedstawiłeś to jest spring tutorial architecture i tylko do tego się nadaje - do tutoriala na youtube. W realnych aplikacjach lepiej inaczej układać kod, wydzielając np. domenowe pakiety/moduły, ewentualnie przy takim stricte warstwowym podejściu 3 warstwy (kontroler-serwis-repo) to jest po prostu za mało i bardzo szybko kod staje się nieutrzymywalny. Bo albo okazuje się, że serwisy muszą wołać serwisy i powstaje koszmarne spaghetti albo przerzucamy dużą część logiki do repozytoriów. Dobre na początek, na pierwsze projekty, żeby zrozumieć Springa. Trzeba mieć po prostu świadomość, że później należy się nauczyć jakiś lepszych podejść. Jeszcze ważna kwestia, żeby myśleć o warstwach w kontekście zespołów klas, a nie pojedynczych klas. Warstwa serwisów to nie znaczy klasa z dopiskiem "Service", a bardziej zespół klas realizujących logikę biznesową (gdzie klasa typu "Serwis" będzie punktem wejścia dla kontrolera)
  2. Bardzo różnie... Przestrzegam tylko przed czymś co może wynikać ze zdania "ten formularz dto jest wysyłany do serwisu w którym jest mapowany do encji" - często z tego wychodzi mapowanie 1:1, a w praktyce mapowanie DTO do entity 1:1 oznacza zazwyczaj zły design domeny.
  3. Taaak. W tutorialach zawsze pokazują UserService, gdzie każda metoda ma 3 linijki, w praktycznym systemie zrobi się 15 metod, każda po 50+ linijek i potem się tego nie da utrzymywać. Serwis per use-case to jest dobre podejście, chociaż wtedy lepiej tego nie nazywać serwisem a np. UseCasem.
  4. Walidacje odbywają się na różnych poziomach - walidacja czy dane pole jest liczbą załatwia framework, walidację typu notnull/notempty możesz załatwić adnotacjami, a walidację biznesową taką jaką jak "czy użytkownik ma 18 lat, żeby zakupić produkty alkoholowe" albo "czy kupon rabatowy można użyć dla danego zamówienia" możesz przeprowadzać już w serwisach lub customowymi walidatorami https://www.baeldung.com/spring-mvc-custom-validator.
2

3 pytanie Aplikacja się rozrasta, załóżmy że nasz user ma dużo funkcji, logowanie, rejestracja, zmiana hasła, usunięcie konta, zmiana nicku, zmiana maila itd. Te wszystkie funkcje trzeba gdzieś zaimplementować, i pierwsze co mi się narzuca to UserService, ale nie wiem czy dobrze myślę bo wtedy taka jedna klasa byłaby po prostu wielkim gniotem na kilkaset lini kodu, możecie doradzić co robi się w takiej sytuacji ? Podzielić to np. na UserRegisterService, UserLoginService, UserChangePasswordService itd. i te wszystkie klasy powinny znajdować się w pakietach user->services->UserRegisterService itd.?

Logikę aplikacji możemy podzielić na kilka sposobów. Ja opiszę dwa moim zdaniem najlpopularniejsze.

  1. To konteksty ograniczone (ang. Bounded Contexts). Jest to pojęcie z Domain Driven Design, które wyznacza granice odpowiedzialności. Przykładowo klasa Product może występować w kilku kontekstach, takich jak ProductCatalog, Orders, Shipment, Payments itd i w każdym kontekście mieć inne zachowania (metody) i atrybuty (pola, właściwości). Dzięki temu zamiast jednej dużej klasy Product mamy kilka mniejszych. Ta sama zasada tyczy się serwisów aplikacyjnych. Serwisy związane z kontekstem IAM (Identity and Access Management) czyli wszystkie logowania, zmiany hasła, uprawnienia będą w paczce Eshop.Application.IAM a serwisy związane na przykład z zamówieniami w paczce Eshop.Application.Orders. I tak, będziesz miał dwie klasy UserService, ale w tym przypadku jest to jak najbardziej ok.

  2. To CQRS gdzie każda metoda serwisu ma swój odpowiednik w postaci komendy i handlera ją wykonującego. Przykład: ChangePassword (komenda) i ChangePasswordHandler (handler). Komendy są wywoływane w kontrolerze i przekazywane do mediatora (Mediator Pattern), który odpowiada za uruchomienie handlera, który został zarejestrowany dla danej komendy. Podobnie w przypadku zapytań, tylko ze zamiast komend mamy kwerendy (ang. Queries) i odpowiadające im handlery. Przykład: FindUser i FindUserHandler. Sposób podziału komend i handlerów jest taki sam jak w punkcie 1 to znaczy po kontekście. I tak komendy/kwerendy wraz z odpowiadającymi im handlerami związane z IAM są w paczce IAM, rzeczy związane z zamówieniami w paczce Orders, z płatnościami w paczcie Payments itd.

Poczytaj o DDD i CQRS. Jest masa materiałów w sieci. I nie słuchaj ludzi którzy mówią że DDD i CQRS jest do dużych projektów ze złożoną logiką biznesową. Część strategiczna czyli modelowanie domeny za pomocą agregatów, encji, value objectów, polityk itd owszem, Natomiast część strategiczna czyli wydzielanie kontekstów ograniczonych, definiowanie języka wszechobecnego w taki sposób, aby kod odwzorowywał język biznesu sprawdza się również w przypadku prostych CRUDów.

4
  1. Mniej więcej tak - kontroler przekazuje żądane gdzieś do domeny (gdzie entry pointem będzie jakiś serwis), domena cośtam z tym robi (może idzie do bazy, może robić coś innego) i zwraca jakiś wynik
  2. To bardzo naiwne podejście - tak to wygląda tylko w CRUDach ;) W praktyce bardzo często model wejściowy (DTO które wysyła user) nijak sie ma do tego co gdzieś masz persystentnie zapisane (np. w bazie) i nijak się ma do tego co potem zwracasz użytkownikowi (jakieś wyjsciowe DTO). Pomyśl o jakimś banalnym przykładzie jak jakaś wyszukiwarka. Inputem od uzytkownika jest formularz z jednym stringowym polem "search query", wyjściem jest np. lista dokumentów które pasują do zapytania, a wewnątrz domeny to jest w ogóle zupelnie inaczej trzymane, np. w jakichś mapach czy drzewach żeby dało się szybko wyszukiwać. Widzisz tu gdzieś jakieś mapowanie 1:1? ;)
  3. Tak, taki podział o którym piszesz jest generalnie konieczny dla czegoś większego niż hello world. Ale granulacja mocno zależy od sytuacji - czasem masz pewne operacje zgrupowane w jednym miejscu, a innym razem będziesz miał osobną klasę na każdy use-case systemu.
  4. Nie da się inaczej :) Walidacja DTO (czy jakieśtam pole nie jest puste) załatwia tylko bardzo podstawowe rzeczy, ale często trzeba request przepchnąć przez pół domeny żeby zorientować się, że coś jest nie tak. Siłą rzeczy taka walidacja będzie wielopoziomowa.

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