Prośba o code review małej aplikacji

Prośba o code review małej aplikacji
PM
  • Rejestracja:prawie 8 lat
  • Ostatnio:7 miesięcy
  • Postów:30
0

Witam
chciałbym prosić o ocenę małej aplikacji. W sumie 4 funkcjonalności:

  • dodawanie postów
  • wyświetlanie postów
  • śledzenie usera
  • wyświetlanie śledzonych postów

Tylko backend(rest api), bez frontu.

https://github.com/pmaciek/social-network-app

Z góry dziękuję
Pozdrawiam
Maciej

edytowany 1x, ostatnio: flowCRANE
baant
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Wrocław
  • Postów:524
1
  1. Po co te buildery do struktur z jednym/dwoma polami
  2. To Thread.sleep() w teście zalatuje brzydkim zapachem
  3. Dokładasz @slf4j a nic nie logujesz
  4. Jak już chcesz tego lomboka to przykładowo na takiej klasie Post wystarczyloby @Value zamiast reszty @ i jeszcze private byś mógł wywalić
  5. Takie chainy stream().sorted().reversed().collect() lepiej sie czyta z góry na dół niż od lewej do prawej. Czyli nowa linia przed każdą kropką

Nie wczytywałem się za bardzo. To tylko kilka rzeczy, które rzuciły mi się w oczy. Ogólnie wygląda ok :)

PM
  • Rejestracja:prawie 8 lat
  • Ostatnio:7 miesięcy
  • Postów:30
0

Dzięki. Poprawiłem.
Threed.sleep musiał zostać, nie znalazłem lepszego sposobu na wymuszenie kolejności postów(czasami dwa sąsiednie posty miały ten sam timestamp)

edytowany 1x, ostatnio: flowCRANE
flowCRANE
Nie cytuj całego posta, jeśli odpowiadasz zaraz pod nim i do jego całości się odnosisz.
baant
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Wrocław
  • Postów:524
3

Zamiast używać LocalDateTime.now() w logice możesz sobie stworzyć jakiś TimeProvider, wstrzykiwać go i pobierać czas z niego. Wtedy w teście można zamockować jakiekolwiek timestampy

TY
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 lata
  • Postów:204
2
  1. Mam nadzieje, że to nie jest wszystko, bo aplikacja jest baaaardzo mała
  2. AddPostRequest to nie jest obiekt żądania, tylko jego body: zmień nazwę albo na AddPostRequestBody albo najprościej na AddPostDto. Mógłbyś też po prostu wrzucić zwyklego stringa w body
  3. Po co @JsonIgnoreProperties(ignoreUnknown = true) @Builder w AddPostRequest
  4. sygnatura metody to followUser a w urlu tracks. Brak konsekwencji
  5. Nie dziel poziomo (czyli paczka z repami, serwisami, modelami, controllerami), bo jak skończysz z 30 repozytoriami, to sie zgubisz. I nie masz mozliwości blokowania dostępu, tylko pionowo. Również podziel swoje god-objecty per ficzer (osobny controller od obslugi postow, osobny od userow)
  • user
    -- User
    -- UserRepo
    -- UserService
    -- UserController
  • post
    -- Post
    -- PostRepo
    -- PostService
    -- PostController
  1. SocialNetworkAppApplication?? AplikacjaApkaSiećSpołeczna? Po co powtarzasz nazwę?
  2. PostsRepository -> PostRepository. Tylko to nie jest repozytorium postów. To jest repozytorium wszechrzeczy.
  3. Co niby ma zwracać followUser, co to za zbiór stringów? Nie ma to sensu.
  4. getPostsByUserName przyjmuje w parametrze id zamiast name
  5. ExceptionHandlerController Błąd walidacji to zazwyczaj 422, a nie 400. 400 jest wtedy, gdy nie jesteś w stanie wejść poprawnie do kontrollera
  6. Po co POST "/users/{userId}/posts" zwraca jakąś liste Postów? Zwróć voida (albo id) oraz 201
  7. "/users/{userId}/tracks/{userIdToFollow}" zwróć 201
  8. "Chopuj" buildery
  9. Jak nie ma użytkownika, to poponuje rzucic exception "EntityNotFound" czy coś, a nie walidation exception. I wtedy zwracać RESTem 404
  10. Obsługę foreign-key powinieneś zaimplementować w repozytorium (czyli sprawdzanie userId)
  11. Po co metoda getSocialNetworkValidationExceptionSupplier zwraca suppliera? Niech zwraca exception i wtedy zrobisz () -> getSocialNetworkValidationExceptionSupplier(userId) w orElseThrow
  12. Byłoby fajnie jakby error code był enumem, czy coś. Żeby to faktycznie był code
  13. Swoje mapy w repozytorium wszechrzeczy ponazywaj tak, zeby bylo wiadomo co jest kluczem. Np usersByUserId
  14. Możesz użyć "dwu-dwukropkowej" lambdy w getFollowedPostsByUserName
  15. zamiast isUserPresent proponuje użyć słowa klucza exists, np: existsById
  16. Jedyne co robi getFollowedUsersByUserId to wywołuje retrieveFollowedUsersByUserId. Obi są prywatne. Po co?
  17. Używaj modyfikatorów dostępu. Domyślnie defaultowy (czyli żaden). Jak już musisz udostępnić coś poza paczkę, to tylko serwis (a najlepiej jego interfejs), potrzebne POJOsy i wyjątki. Nie widze powodu, dla ktorego kontrollery moglyby byc publiczne
  18. Powiel testy na warstwe. Testy controllera/serwisu/repo. Zrób je unitami, czyli nie stawiaj calego wielkiego kontekstu springa, bo kiedys czegos zapomnisz zamockować. Do testów controllera masz springowe "mockMvc"
  19. Jak już będziesz miał unity, to możesz zrobić testy integracyjne za pomocą @SpringBootTest i TestRestTemplate. Ale w sumie nie musisz
  20. Ale nie rób ich dziedzicząc po magicznej klasie. Generalnie unikaj dziedziczenia i rozwiąż to kompozycją
  21. No i twoja klasa abstrakcyjna w sumie nie ma abstrakcji. To tylko kolejny god-object z wszystkimi możliwymi żądaniami jakie istnieją. Po co SocialNetworkAppApplicationAddPostsTests ma mieć dostęp do żądania callFollowUser??
  22. Przydałby się jeden test integracyjny, który sprawdza, czy apka wstaje. Samo @SpringBootTest i jeden pusty test

TLDR:
Najpierw popraw architekturę, podziel poziomo (per ficzer). Reszta wyjdzie z tego sama

baant
19. czyt. Metoda referencyjna. Łatwiej w guglu znaleźć niż dwu-dwukropkowa lambda :D
TY
O dzięki, nawet nie wiedziałem =D
PM
  • Rejestracja:prawie 8 lat
  • Ostatnio:7 miesięcy
  • Postów:30
0

Dzięki,
... co do tej architektury per feature mam wątpliwości, nie wiem czy to nie będzie przerost formy na trescia, bo w sumie nie mam żadnej funkcjonalności związanej z użytkownikami wystawionej na zewnątrz, jest to tylko kontener w wartwie repo, aby sprawdzic czy dany user istnieje czy nie. Userzy są dodawani automatycznie przy pierwszym poscie. Jak sądzisz ?

baant
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Wrocław
  • Postów:524
0

Myśle, że przy tak małej aplikacji nie ma to znaczenia. Przy większych takie paczki mają już sens. Potem dużo łatwiej z takiej paczki zrobić osobny moduł(np mavenowy), a moduł można wyjąć jako osobny (micro)serwis :) Przy pakowaniu wszystkich repo do paczki repo, wszystkich kontrolerów do paczki kontrolerów wydzielenie jakiegoś modułu/serwisu to by była mordęga

PM
  • Rejestracja:prawie 8 lat
  • Ostatnio:7 miesięcy
  • Postów:30
0

Poprawiłem większość punktów, łacznie z podziałem pakietów na per feature. Myślę, że wygląda to teraz dużo lepiej.
Dzięki wszystkim za uwagi.

TY
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 lata
  • Postów:204
1

Jedziemy dalej:

  1. w UserController brakuje ci <> przy ResponseEntity. Tego typu błędy wyłapuje IntelliJ, nie rozumiem czemu w ogóle to masz
  2. A tak w sumie, to nie potrzebujesz ResponseEntity, tylko możesz zwrócić User i zaadnotować to @ResponseStatus(201). To samo w PostController
  3. Czemu pola User nie są prywatne? Czemu obiekt nie jest immutable?
  4. Twój UserEntity tylko udaje immutable, bo ma w polu mutable Set
  5. Mieszasz konfigurację za pomocą klas @Configuration i adnotacji nad klasami. Zdecyduj się na jedno rozwiązanie, żeby było konsekwentnie
  6. Po co metoda mapUserEntityToUser zwraca funkcje, zamiast po prostu przyjąć UserEntity w parametrze? KISS
  7. Chopuj buildery
  8. W InMemoryUsersRepository#trackUser user i userEntity to to samo =P. Zrobisz dwa razy fetcha na mapie
  9. Brzydko nazywasz paczki. Repozytorium nie należy do domeny biznesowej. Encje też. Natomiast obiekt biznesowy (Post, User) już tak. Tylko przypadkiem jest on jednocześnie DTOsem.
  10. retrievePostsByUserId może wzrócić nulla i wtedy getPostsByUserIds walnie NPE
  11. Nie iniektujesz poprawnego LocalDateTimeProvider w postsRepository
  12. Osobiście przeniósłbym metodę getUser z PostFacade do UserFacade (tą która zwraca czystego Usera). I schował UserNotFoundException do paczki users. I schował ExceptionHandlerController (de facto UserExceptionHandlerController) do paczki users. Masz wtedy całą obsługę użytkowników zamkniętą w jednej paczce
PM
  • Rejestracja:prawie 8 lat
  • Ostatnio:7 miesięcy
  • Postów:30
0
Tyvrel napisał(a):

Jedziemy dalej:

  1. w UserController brakuje ci <> przy ResponseEntity. Tego typu błędy wyłapuje IntelliJ, nie rozumiem czemu w ogóle to masz
  2. A tak w sumie, to nie potrzebujesz ResponseEntity, tylko możesz zwrócić User i zaadnotować to @ResponseStatus(201). To samo w PostController

Racja, tak będzie lepiej

  1. Czemu pola User nie są prywatne? Czemu obiekt nie jest immutable?

Przeoczenie, zapomniałem zmienić gdy usunąłem odnotację @Value

  1. Twój UserEntity tylko udaje immutable, bo ma w polu mutable Set

Ok, chciałem mieć łatwiejszy sposób aktualizowania userów, ale może lepiej nadpisywać obiekty przy aktualizacji zamiast mutować

  1. Mieszasz konfigurację za pomocą klas @Configuration i adnotacji nad klasami. Zdecyduj się na jedno rozwiązanie, żeby było konsekwentnie

Tego nie rozumiem, @Configuration jest ze springa, a adnotacje nad klasami z lomboka, nie używam annotation based configuration nigdzie.

  1. Po co metoda mapUserEntityToUser zwraca funkcje, zamiast po prostu przyjąć UserEntity w parametrze? KISS
  2. Chopuj buildery

Co to znaczy?

  1. W InMemoryUsersRepository#trackUser user i userEntity to to samo =P. Zrobisz dwa razy fetcha na mapie
  2. Brzydko nazywasz paczki. Repozytorium nie należy do domeny biznesowej. Encje też. Natomiast obiekt biznesowy (Post, User) już tak. Tylko przypadkiem jest on jednocześnie DTOsem.

Wzorowałem się na przykładzie Jakuba :)

  1. retrievePostsByUserId może wzrócić nulla i wtedy getPostsByUserIds walnie NPE
  2. Nie iniektujesz poprawnego LocalDateTimeProvider w postsRepository

Dzięki, przeoczenie.

  1. Osobiście przeniósłbym metodę getUser z PostFacade do UserFacade (tą która zwraca czystego Usera). I schował UserNotFoundException do paczki users. I schował ExceptionHandlerController (de facto UserExceptionHandlerController) do paczki users. Masz wtedy całą obsługę użytkowników zamkniętą w jednej paczce
TY
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 lata
  • Postów:204
0
  1. Hmm, w sumie racja. Używasz component scana tylko do controllerów
  2. Chopowanie to sposób formatowania chainowanych metod
Kopiuj
return PostEntity.builder().userId(post.getUserId()).postDate(getLocalDateNow()).message(post.getMessage()).build();
.
return PostEntity.builder()
		.userId(post.getUserId())
		.postDate(getLocalDateNow())
		.message(post.getMessage())
		.build();
edytowany 6x, ostatnio: Tyvrel
PM
  • Rejestracja:prawie 8 lat
  • Ostatnio:7 miesięcy
  • Postów:30
0

W sumie nie podoba mi się to @Configuration. Ogranicza dostęp tylko do klas publicznych i z tego samego pakietu. A gdybym chciał wydzielić wiecej podpakietów musiałbym tworzyć odrębne klasy @Configuration dla każdego pakietu w którym mam niepubliczne klasy("beany"). Inaczej jak annotation based configuration który scanuje wszystko by default.

edytowany 1x, ostatnio: p_maciek
TY
Wydaje mi się, że podejście Annotation based configuration (w sensie bez @Configuration) jest dużo bardziej rozpowszechnione. Oba rozwiązania są równie dobre (i równie złe), chodzi tylko o to żeby trzymać się jednego
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)