Modułowość w architekturze projektu, Spring Security i komunikacja z frontem

0

Hej! Wstrzymywałem się z założeniem tego tematu przez długi czas, najpierw chciałem stworzyć gotową aplikację i dopiero wtedy zadać kilka pytań - jednak mam w głowie tyle niejasności, że chciałbym je rozwiać już teraz.
Tekstu jest sporo a że szanuję wasz czas i nie każdy ma ochotę czytać tyle głupot to zaznaczyłem miejsca w których stawiam pytania.
Dzięki za każdą wskazówkę!

Projekt o którym mowa: https://github.com/xsiir/DietManager

  1. Na forum natrafiłem na post w którym chwalony był wykład pana Jakuba Nabrdalika Modularity and hexagonal architecture . Oglądnąłem kilka razy, starałem się zrozumieć jak najwięcej i stworzyć projekt według tych zasad o których mowa w nagraniu. Wydaje mi się jednak, że nie potrafię przełożyć teorii na praktykę. Jakby cała moja wcześniejsza nauka programowania w Javie była oparta na wiecznym dodawaniu modyfikatorów private i public, to było dla mnie takie... naturalne. Podobnie zawsze dzieliłem aplikację na trzy pakiety: services, controllers i repository. A teraz? Mętlik. W teorii miało mi to ułatwić pracę a ja czuję się czasem zagubiony.
    **Pytanie: ** 1. Czy https://github.com/xsiir/DietManager/blob/5b1beb0c21553c901fcd363bfea24803710dbfeb/src/pl/sienkiewicz/user/domain/UserFacade.java#L17 takie metody mogą mieć miejsce? Przez to, że dla mnie wygląda to inaczej niż wszystko co zwykłem widzieć, mam wrażenie że jest źle. Czy ja w ogóle robię to chociaż w małym stopniu dobrze? XD. 2. Do jednego modułu wrzucać wszystko co z niego korzysta? Czy w ogóle te dwa które wyznaczyłem są okej?

  2. Do tej pory do pisania frontu używałem JSP. Od tego projektu zacząłem pracować z Reactem. W jaki sposób udostępnić taki projekt na gicie? Front powinien być drugim repozytorium czy zamieścić go już w tym istniejącym z back-endem?

  3. Z racji tego, że w warstwie bazy danych implementacja metod get, save, delete wyglądały identycznie to postanowiłem, że stworzę taką abstrakcyjną klasę https://github.com/xsiir/DietManager/blob/master/src/pl/sienkiewicz/utils/CRUDOperations.java która będzie rozszerzać poszczególne repozytoria które będą tego potrzebowały. Takie rozwiązanie jest okej? No i jak już przy tym jesteśmy: dalej w kodzie korzystam z Optional. Na ten moment robię to w ten sposób, że z fasady wywołuję sobie metodę która wyciągnie mi dajmy na to użytkownika i dopiero wtedy zwracam Optional.valueOf(ten_uzytkownik). Tak jest dobrze? Pytanie: Czy lepiej byłoby gdyby już sama warstwa bazy danych zwracała mi Optional<User> zamiast po prostu User? Dla zobrazowania, mowa o tym momencie: https://github.com/xsiir/DietManager/blob/1fb4f613d79349c290ab2e886272e6e145128023/src/pl/sienkiewicz/user/domain/UserFacade.java#L31

  1. Nie rozumiem jeszcze Spring Security. Wszystko co jest w projekcie jest przepisane z jakiegoś tutorialu znalezionego w sieci. Sporej części która jest jest tam zaimplementowana nie rozumiem. Pewnie dlatego właśnie nie działa tak jak chciałem żeby działało.
    W JSP potrafiłem jakoś imitować sesję zalogowanego użytkownika przez jakieś session scope - natomiast teraz kiedy mam osobno front to nie mam pojęcia jak to zrobić - tym bardziej że jestem blady w React'cie. Chciałbym jednak spytać o samą zasadę, jak front powinien współpracować z serwerem.

title

Pytanie: Chciałbym, żeby "Twoje posiłki" były dostępne tylko dla użytkownika zalogowanego. Jak powinna wyglądać taka komunikacja? Po wciśnięciu buttona powinienem wysłać żądanie do serwera z pytaniem czy dla bieżącej sesji użytkownik jest zalogowany i jeśli tak to wyświetlić widok a jeśli nie to przekierować do strony z logowaniem?

Każdą inną uwagę też docenię. :))

2

Czemu używasz gosna do konwertowania obiektów na jsona? Spring korzysta z jacksona, który spokojnie wystarcza. Zrób kilka tutoriali stąd https://spring.io/guides odnośnie budowania aplikacji REST oraz ze Spring Data i Security. To co potrzebujesz to budowanie backandu REST w Spring Boot, a nie Spring MVC.

Co do Security mi bardzo pomógł zrozumieć temat ten wykład lub poszukaj inne tego gościa, gdzie są slajdy lepiej widoczne.
Skoro frontend w reactie to polecam użyć JWT. Zrób sobie ten przykład: https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/

A więc bedziesz miał dwa oddzielne projekty, spring boot z backendem, który wypluwa resty oraz react, w którym klepiesz front.

Backend będzie Ci zwracał token JWT, który zapisujesz gdzieś po stronie klienta (local storage czy cookie jest dyskusyjne) i dołączasz go do każdego requestu, backend wyciąga sobie dane usera z tokenu i jak ok to zwracasz sobie dane albo 401 Unauthorized.

W ractie też musisz mieć jakieś security, poszukaj coś o React JWT i na pewno znajdzie się jakis tutorial. Więc odnośnie ostatniego pytania to przycisk powinien być wyświetlany tylko dla zalogowanego (robisz to w reactie), a rest zwraca z posilkami tylko jak request po nie będzie z poprawnym JWT.

0

super! takiego naprowadzenia potrzebowałem. dziękuję :)

4
  1. Dobrze podzieliłeś. Ale dalej ci się plączą gdzieś tam niepotrzebne public.
    Masz moduł Meal. Żaden inny moduł z niego nie korzysta, więc nie potrzebujesz udostępniać nic na zewnątrz. Więc nie potrzebujesz fasady. Serwis z którego korzysta controller powinien być package-private. Wszystko może być package-private (nie wiem jak jest z POJOsami w JPA =P)
    Masz też moduł User. O module User wie i korzysta moduł Meal. Więc potrzebujesz udostępnić publicznie encję User oraz jakieś metody, z których korzysta moduł Meal. W sumie na razie nie ma żadnych, więc możesz śmiało usunąć tą fasade do momentu, w którym moduł Meal będzie używał modułu User. Pamietaj, że w fasadzie są tylko metody z których korzystają inne moduły. Nie udostępniaj publicznie innych metod (na przykład tych, z których korzysta tylko controller)
    Robisz też jeszcze jeden podstawowy błąd. Łączysz w postaci encji User dwie domeny: Usera w kontekście Security (to się chyba subject nazywało), który ma hasło, login i inne śmieci oraz Usera w kontekście grubasa który chce się odchudzić, który ma wagę, wysokość, bmi, itd. Powinieneś rozdzielić to na osobne moduły.

  2. Wszystko jedno. Możesz trzymać w jednym repo, który ma dwa projekty /web i /server (a potem może /mobile i /desktop). Albo możesz mieć kilka repozytorów. Oba sposoby mają swoje wady. Albo masz duże, cięzkie mono-repozytorium, albo problemy z wersjonowaniem

  3. Niech baza danych zwraca Optionala. Unikaj jakichkolwiek przejawów null w kodzie. Wydzielanie wspólnej częsci kodu, to osobnych klas, jest bardzo dobrym zachowaniem. Można to zrobić przez klasę abstrakcyjną, co ma swoje wady, ale jest ok. Generalnie dziedziczenia używaj w ostateczności, preferuj kompozycję

  4. Idzie bezkolizyjnie używać Spring Security z Reactem, czy Angularem, możesz smiało używać Session-based authentication. Wyszukaj sobie Spring Security and Angular/React, były materiały na ten temat.
    Musisz zasetować login/logout handlery, żeby ci zwracały normalny status code zamiast przekierowania. A w aplikacji frontowej, jak wyślesz żądanie po zalogowanie, to w zwrotce dostaniesz ciastko sesji, którego używaj do wysyłania żądań. Jak ktoś ci wyśle złe ciasteczko sesji, to zwróć 401 i w aplikacji frontowej przerzuć widok na widok logowania.

PS: Nie widziałem poma. Używasz mavena lub gradlea?

0

Dzięki, że postanowiłeś aż tak się rozczulić nad tym kodem! To bardzo miłe, tym bardziej że przez dłuższy czas gdzieś tam czułem się pogubiony w tym wszystkim.

W projekcie używam mavena ale .gitignote ukrył poma. W zasadzie nie bardzo wiem jakie pliki powinno się maskować upubliczniając projekt, dlatego przekopiowałem jakiegoś gotowca z sieci .

0

pom powinien być w repo, żeby każdy budował projekt w ten sam sposób

0

Nie korzystasz z całego dobrodziejstwa Optional:

zamiast :


Optional<User> optionalUser = userFacade.getUser(email);
		String imie = null;
		
		if(!optionalUser.isPresent()) {
			return "NIE ISTNIEJE!";
		}else {
			imie = optionalUser.get().getFirstname();
		}
		
		return imie;

wystarczy jedna linijka:

return userFacade.getUser(email).map(User::firstName).orElse("NIE ISTNIEJE!");

...to samo w pozostałych metodach UserController

0

Metoda saveUser https://github.com/xsiir/DietManager/blob/1fb4f613d79349c290ab2e886272e6e145128023/src/pl/sienkiewicz/user/domain/UserFacade.java#L17 łamie zasadę SRP: wylicza BMI i zapisuje usera. Powinneś rozdzielić te rzeczy.

1
Tyvrel napisał(a):

Serwis z którego korzysta controller powinien być package-private. Wszystko może być package-private (nie wiem jak jest z POJOsami w JPA =P)

kontroler to warstwa infrastruktury, serwis natomiast warstwa domeny - nie powinny byc mieszane, stad serwis powinien byc publiczny by mógł z niego korzystac kontroler. generalnie, jeżeli kontroler korzysta z serwisu to znaczy ze 'swiat' z niego korzysta, wiec IMHO bardzo spoko jest to zaznaczyc modyfikatorem 'public'

Co do projektu - fasada, która zwraca Jsona jako string jest mocno średnim pomysłem, skoro już chcesz sie bawic w domeny i moduły to niech fasada zwróci obiekt domenowy, a warstwa infrastruktury sobie to przerobi na co bedzie jej potrzebne - np. na xmla. Polecam też dopisac testy, wtedy by to szybko wyszło, gdyby test domenowy musiał z powrotem parsowac jsona na obiekt zeby sprawdzic czy dostales czego chciales

0
p_maciek napisał(a):

Nie korzystasz z całego dobrodziejstwa Optional:

wystarczy jedna linijka:

return userFacade.getUser(email).map(User::firstName).orElse("NIE ISTNIEJE!");

Tak, to prawda. Nie do końca czuję to mapowanie a mój kod jest kilka wersji Javy do tyłuD ale powoli się przełamuję!

p_maciek napisał(a):

Metoda saveUser https://github.com/xsiir/Diet[...]er/domain/UserFacade.java#L17 łamie zasadę SRP: wylicza BMI i zapisuje usera. Powinneś rozdzielić te rzeczy.

Zatem w którym momencie powinienem był obliczyć i ustawić bmi? Miałem taką myśl, żeby w samej bazie danych dodać jakiś wyzwalacz który by to liczył jednak wolałem zostać przy obliczeniach po stronie serwera.

hcubyc napisał(a):

Co do projektu - fasada, która zwraca Jsona jako string jest mocno średnim pomysłem, skoro już chcesz sie bawic w domeny i moduły to niech fasada zwróci obiekt domenowy, a warstwa infrastruktury sobie to przerobi na co bedzie jej potrzebne - np. na xmla.

Faktycznie, brzmi logiczniej. Dzięki! A co do testów to jaka jest dobra praktyka w ich pisaniu? Po każdej jednej skończonej funkcjonalności pisać test?

0

Faktycznie, brzmi logiczniej. Dzięki! A co do testów to jaka jest dobra praktyka w ich pisaniu? Po każdej jednej skończonej funkcjonalności pisać test?

Tak.

Zatem w którym momencie powinienem był obliczyć i ustawić bmi? Miałem taką myśl, żeby w samej bazie danych dodać jakiś wyzwalacz który by to liczył jednak wolałem zostać przy obliczeniach po stronie serwera.

Baza danych to detal twojego projektu, nie przywiązuj sie do niego. Obliczenie BMI to u ciebie 'logika biznesowa' zatem powinna być w domenie. Najprosciej to serwis, który będzie to liczył, stopien trudnosci wyzej to znalezc pasujacy obiekt domenowy (encje lub agregat) i w nim umiescic te logike, chociaz nie wszystko da rade tam wepchnac

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