Simple REST web service

Odpowiedz Nowy wątek
2020-03-11 17:00

Rejestracja: 3 tygodnie temu

Ostatnio: 2 dni temu

0

Cześć, jestem młodym studentem IT programującym głównie w Javie.

Napisałem prostą usługę typu REST z wykorzystaniem Spring Boot'a oraz architektury DDD. Projektem jest system zarządzania biblioteką pozwalający na przeglądanie, rezerwację i wypożyczanie książek.

Byłbym wdzięczny o ocenę projektu i stylu kodowania przez bardziej doświadczonych programistów.

Całe repozytorium projektu: library-backend
Bliźniaczy temat na stackexchange: Simple REST web service

wow, fajne readme - danek 2020-03-11 17:02

Pozostało 580 znaków

2020-03-14 19:39

Rejestracja: 5 lat temu

Ostatnio: 39 minut temu

Lokalizacja: Warszawa

2

@eziomou:
No i tu pojawia się problem pt DDD + JPA. Powiem szczerze, na samym DDD średnio się znam ale występuje problem mieszania logiki biznesowej z frameworkiem i do tego tak "cięzkim" jak JPA. Dodatkowo taki styl narzuca mutowalność, a mutowalność w logice biznesowej na ogół jest zła. Rozumiem mutowalny stan np. socketa, okienka w Swingu (choć akurat w Javie GUI się już za bardzo nie pisze) ale moim zdaniem od czegoś takiego jak


book.reserve() {
     book.reserveDate = LocalDateTime.now(clock);
}

lepsze jest


book.reserve() {
  return book.withReserveDate(LocalDateTime.now(clock))
}

Obiekty które reprezentuję domenę/logikę biznesową powinny być niemutowalne, i własnie dlatego nie lubie klasycznego podejścia do DDD. Wydaje mi się że rozwiązaniem moich problemów jest architektura hexagonalna :D


Nie pomagam przez PM. Pytania zadaje się na forum.
edytowany 2x, ostatnio: scibi92, 2020-03-14 19:44
DDD przecież może być niemutowalne, a i z hexagonal się raczej nie kłóci. - somekind 2020-03-15 02:22

Pozostało 580 znaków

2020-03-15 00:07

Rejestracja: 3 tygodnie temu

Ostatnio: 2 dni temu

0
scibi92 napisał(a):

@eziomou:
No i tu pojawia się problem pt DDD + JPA. Powiem szczerze, na samym DDD średnio się znam ale występuje problem mieszania logiki biznesowej z frameworkiem i do tego tak "cięzkim" jak JPA. Dodatkowo taki styl narzuca mutowalność, a mutowalność w logice biznesowej na ogół jest zła. Rozumiem mutowalny stan np. socketa, okienka w Swingu (choć akurat w Javie GUI się już za bardzo nie pisze) ale moim zdaniem od czegoś takiego jak


book.reserve() {
     book.reserveDate = LocalDateTime.now(clock);
}

lepsze jest


book.reserve() {
  return book.withReserveDate(LocalDateTime.now(clock))
}

Obiekty które reprezentuję domenę/logikę biznesową powinny być niemutowalne, i własnie dlatego nie lubie klasycznego podejścia do DDD. Wydaje mi się że rozwiązaniem moich problemów jest architektura hexagonalna :D

Dziękuje za wypowiedź i przejrzenie kodu.

Mógłbyś dokładnie napisać w którym miejscu w kodzie występuje mieszanie logiki biznesowej z frameworkiem oraz szersze uzasadnienie reguły, że obiekty logiki biznesowej powinny być niezmienne?

Książka zaprojektowana jest w ten sposób, dlatego że reprezentuje fizyczną kopie książki występującej w magazynie, jej tożsamość ma znaczenie. Dwie książki o dokładnie tych samych atrybutach ciągle będą innymi książkami, a o ich tożsamości decyduje jedynie identyfikator (w moim przypadku id jest identifykatorem biznesowym). Podobnie jak dwie osoby o tych samych imionach i nazwiskach są ciągle innymi osobami, mają swoją tożsamość oraz ciągłość w czasie.

Gdyby książka była obiektem niezmiennym, w danym momencie działania programu mógłbym mieć dowolną ilość kopii tej samej książki (pod względem tożsamości) o zupełnie innych stanach. Tutaj mógłby wystąpić problem ze stwierdzeniem, która z kopii posiada aktualny stan.

W podejściu DDD stosuje się obiekty niezmienne tzw. Value Object dla obiektów domeny, których tożsamość nie ma znaczenia. Jako przykład mogą posłużyć pięniądze, w większości przypadków nie ma znaczenia tożsamość dwóch obiektów pieniędzy o tej samej wartości, tutaj jak najbardziej mają zastosowanie obiekty niezmienne.

Dodatkowo, wyobraź sobie, że rzeczywiście wszystkie obiekty domeny są niezmienne i akurat tak się zdarzyło, że mamy kilka bardzo ciężkich obietów tzn. takich które mają wiele asocjacji i na każdym z tych obiektów wykonujemy bardzo dużo operacji. W takim przypadku przy każdej operacji tworzona jest głęboka kopia całego drzewa obiektów i być może przez pomyłkę przechowywana gdzieś w pamięci. Byłbym wdzięczny gdbyś uzasadnił stwierdznie, że zmienność obiektów logiki biznesowej jest zła.

Pozostało 580 znaków

2020-03-15 00:40

Rejestracja: 5 lat temu

Ostatnio: 39 minut temu

Lokalizacja: Warszawa

1

Mógłbyś dokładnie napisać w którym miejscu w kodzie występuje mieszanie logiki biznesowej z frameworkiem

Obiekty domenowe są zarządzane przez JPA więc występuje wymieszanie logiki z frameworkiem

Tutaj mógłby wystąpić problem ze stwierdzeniem, która z kopii posiada aktualny stan.

A czym to sie różni od używania mutowalnych obiektów?
Aktualny stan ma ten "najnowszy obiekt", jeżeli masz:

val order = orderRepository.findById(orderId)
val sentOrder = order.withSentDate(date)

to sentOrder jest aktualne

W takim przypadku przy każdej operacji tworzona jest głęboka kopia całego drzewa obiektów i być może przez pomyłkę przechowywana gdzieś w pamięci.

Nieprawda. W przypadku gdy mamy niezmienne obiekty możemy łatwo współdzielić różne obiekty między sobą. Jeśli mamy obiekt order który ma liste itemów, to gdy tworzymy nowy order który ma tylko inny status nie musimy w ogóle kopiować "głęboko" tej listy, kopiujemy tylko referencje - zakładając że mamy niemutowalne kolekcje jak Vavr czy ArrowKt.

Byłbym wdzięczny gdbyś uzasadnił stwierdznie, że zmienność obiektów logiki biznesowej jest zła.

Napisałem o tym tutaj:
https://artegence.com/blog/be[...]lity-in-software-development/
Teraz bym dodał element o lokalnym wnioskowaniu - nie musisz wszędzie sprawdzac stanu obiektu przy debugowaniu :)
Pozdrawiam


Nie pomagam przez PM. Pytania zadaje się na forum.

Pozostało 580 znaków

2020-03-15 01:52

Rejestracja: 3 tygodnie temu

Ostatnio: 2 dni temu

0

Obiekty domenowe są zarządzane przez JPA więc występuje wymieszanie logiki z frameworkiem

Fakt, że JPA zarządza obiektami domeny nie ma wpływy na logikę biznesową. Na poziomie kodu obiekty domeny mają świadomość tylko o adnotacjach. Podczas pisania warstwy domeny jedynym wymogiem stawianym przez JPA na który zwracałem uwagę był bezargumentowy konstruktor.

A czym to sie różni od używania mutowalnych obiektów?
Aktualny stan ma ten "najnowszy obiekt", jeżeli masz:

val order = orderRepository.findById(orderId)
val sentOrder = order.withSentDate(date)

Kiedy mutawalny obiekt jest już stworzony pracujemy na jednej instancji, jasne w takim prostym przykładzie tego nie widać, chodziło mi o bardziej złożoną sytuację gdzie np. instancja jest przekazywana wiele razy do różnych metod (wymyślam).

Nieprawda. W przypadku gdy mamy niezmienne obiekty możemy łatwo współdzielić różne obiekty między sobą. Jeśli mamy obiekt order który ma liste itemów, to gdy tworzymy nowy order który ma tylko inny status nie musimy w ogóle kopiować "głęboko" tej listy, kopiujemy tylko referencje - zakładając że mamy niemutowalne kolekcje jak Vavr czy ArrowKt.

Jasne, pod warunkiem że mamy niezmienne kolekcje.

Przeczytałem artykuł, rozumiem, że u siebie szeroko stosujesz obiekty niezmienne i masz tutaj doświadczenie w większych projektach. Czy kopiowanie złożonych obiektów po każdej operacji we wszystkich klasach nie zaciemnia logiki, mam na myśli stosunek ilości kodu poświęconego na kopiowanie do ilości kodu właściwej logiki?

edytowany 3x, ostatnio: eziomou, 2020-03-15 02:55

Pozostało 580 znaków

2020-03-15 21:50

Rejestracja: 5 lat temu

Ostatnio: 39 minut temu

Lokalizacja: Warszawa

1

Fakt, że JPA zarządza obiektami domeny nie ma wpływy na logikę biznesową. Na poziomie kodu obiekty domeny mają świadomość tylko o adnotacjach. Podczas pisania warstwy domeny jedynym wymogiem stawianym przez JPA na który zwracałem uwagę był bezargumentowy konstruktor.

Oczywiście że ma, pomiając już ten bezargumentowy konstruktor (bardzo słabe) to jeszcze jest cykl życia encji, różne jakieś dziwne stany, proxy JPA itp
No i generalnie to antypattern mieszać logikę z frameworkiem, rdzeń aplikacji powinien być raczej odporny na frameworki, no chyba że piszemy jakieś proste CRUDy, tylko że do tego już DDD nie potrzebujemy

chodziło mi o bardziej złożoną sytuację gdzie np. instancja jest przekazywana wiele razy do różnych metod (wymyślam).

Opcje są 2 - albo nie zmieniasz argumentu i nie istnieje żaden problem, albo zmieniasz i wtedy nie masz czystych funkcjji i to że tak chcesz zrobić jest problemem.

Jasne, pod warunkiem że mamy niezmienne kolekcje.

Java - Vavr, Kotlin -ArrowKt

Czy kopiowanie złożonych obiektów po każdej operacji we wszystkich klasach nie zaciemnia logiki, mam na myśli stosunek ilości kodu poświęconego na kopiowanie do ilości kodu właściwej logiki?

A czy settery nie zaciemniają? Kopiowanie ułatwia de facto ogarniecie kodu, polecam:


Nie pomagam przez PM. Pytania zadaje się na forum.
Hmmm, ja tego czlowieka chyba gdzies widzialem... - WhiteLightning 2020-03-15 23:11

Pozostało 580 znaków

2020-03-15 23:10

Rejestracja: 3 tygodnie temu

Ostatnio: 2 dni temu

0

Oczywiście że ma, pomiając już ten bezargumentowy konstruktor (bardzo słabe) to jeszcze jest cykl życia encji, różne jakieś dziwne stany, proxy JPA itp

Na poziomie warstwy domeny obiekty zachowują się jak zwykle POJO, z czymś takim jak cykl życia encji (jeśli chodzi Ci o encję w kontekście JPA, a nie DDD) mamy do czynienia dopiero na poziomie warstwy aplikacji więc nie wiem jaki wpływ na logikę biznesową, oprócz tego który wymieniłem wcześniej masz na myśli. W warstwie domeny istnieje jedynie abstrakcja repozytoriów implementowana przez warstwę niżej.

A czy settery nie zaciemniają? Kopiowanie ułatwia de facto ogarniecie kodu, polecam:

W DDD, jak może zauważyłeś w moim kodzie nie stosuje się głupich getterów i setterów zwracających i ustawiających pola, które nie zawierają żadnej logiki, tylko metody, które w jasny sposób opisują daną czynność, więc w żadnym stopniu nie zaciemniają one logiki biznesowej i dodatkowo powinny być atomowe czyli po wykonaniu dowolnej operacji stan systemu powinien być spójny, np. changePassword(String newPassword) czy submitSomething(Object something);

Pozostało 580 znaków

2020-03-16 00:28

Rejestracja: 5 lat temu

Ostatnio: 39 minut temu

Lokalizacja: Warszawa

1

mamy do czynienia dopiero na poziomie warstwy aplikacji więc nie wiem jaki wpływ na logikę biznesową, oprócz tego który wymieniłem wcześniej masz na myśli. W warstwie domeny istnieje jedynie abstrakcja repozytoriów implementowana przez warstwę niżej.

Ma to że nie jest to już obiekt domenowy niezależny od od warstwy perzystencji tylko jest zakażona adnotacją JPA, jest juz obiektem proxy. Jeśli już stosować rakotwórcze JPA to w wartstwie infrastruktury

W DDD, jak może zauważyłeś w moim kodzie nie stosuje się głupich getterów i setterów zwracających i ustawiających pola,

No dobrze, w czym to:

issue.assignee(userId)

jest lepsze od:


val assignedIssue = issue.assignee(userId)

Nie pomagam przez PM. Pytania zadaje się na forum.

Pozostało 580 znaków

2020-03-16 21:01

Rejestracja: 3 tygodnie temu

Ostatnio: 2 dni temu

0

Ma to że nie jest to już obiekt domenowy niezależny od od warstwy perzystencji tylko jest zakażona adnotacją > JPA, jest juz obiektem proxy. Jeśli już stosować rakotwórcze JPA to w wartstwie infrastruktury

Jasne, o adnotacjach pisałem wyżej. W pełni się zgadzam, wiedza o ORM w tym wypadku jest zawarta w obiektach domeny jednak nie ma to żadnego wpływu na logikę biznesową, obiekt po usunięciu adnotacji będzie się zachowywać dokladnie tak samo, obiektem proxy staje się dopiero w warstwie aplikacji kiedy do gry rzeczywiście wkracza jpa.
Nie ma nic za darmo, jeśli komuś bardzo zależy na JPA i jak napisałeś braku "zakażonych" obiektów, które zawierają kilka adnotacji to można wykorzystać starą konfigurację w xmlu bez użycia adnotacji.

No dobrze, w czym to:

issue.assignee(userId)
jest lepsze od:

val assignedIssue = issue.assignee(userId)

Nigdzie nie napisałem, że coś jest lepsze lub gorsze, każde rozwiązanie ma swoje wady i zalety jednak uważam, że nie należy stosować jednego lub drugiego rozwiąnia na siłe tam gdzie nie ma takiej potrzeby.

edytowany 1x, ostatnio: eziomou, 2020-03-16 21:04

Pozostało 580 znaków

2020-03-16 21:11

Rejestracja: 12 lat temu

Ostatnio: 1 godzina temu

1

Co stoi na przeszkodzie wyizolować JPA za pomocą portów i adapterów (pomijając czy ma to sens w danej aplikacji)? Wtedy repozytoria zwracają i przyjmują obiekty domenowe (agregaty, encje), które są mapowane na te znienawidzone (zapewne z wzajemnością) encje JPA.


Ivory Tower Architect

Pozostało 580 znaków

2020-03-16 21:23

Rejestracja: 3 tygodnie temu

Ostatnio: 2 dni temu

0

@Charles_Ray: Może nakład pracy związany z Twoim rozwiązaniem, a usunięciu adnotacji JPA nawet z dużego projektu w przypadku zmiany dostawcy.

edytowany 1x, ostatnio: eziomou, 2020-03-16 21:26

Pozostało 580 znaków

Odpowiedz

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