Od ostatniego wpisu wiele rzeczy w aplikacji uległo zmianie. Tak bardzo wchłonąłem się w proces programowania, że nie wszystko wygląda idealnie z punktu widzenia zarządzania. Zamiast trzymać się wyznaczonych zadań i ścieżki ich odhaczania to leciałem z pisaniem kodu jak szalony. Z jednej strony jest to dobre, ponieważ bawiłem się przy tym jak nigdy. Mogłem puścić wodze fantazji i tworzyć to na co mam ochotę w sposób jaki mi odpowiada. Przez tą frywolność ucierpiała jednak dokumentacja mojej drogi. Cały napisany kod jaki napisałem zrobiłem w ramach jednego zadania o nazwie “Move from JPA to JDBC Spring Data”. Nie da się oszukać, że kategorii zmian było o wiele więcej. Z tego powodu ten wpis może być trochę chaotyczny w swoim przekazie. Zapraszam do lektury!

Problem ze zdarzeniami

Początkowy zamysł architektoniczny opierał się o dwa moduły - catalogue oraz proposal, pomiędzy którymi latały eventy w obydwie strony. Dokładniej zostało to zobrazowane w poprzednim artukule. W skrócie ujmując, gdy dodawaliśmy zwierzaka do catalogue od razu leciało zdarzenie o tym fakcie do proposal. Natomiast, gdy ktoś zaakceptował wniosek to dowiadywał się o tym moduł catalogue i blokował edycję danego zwierzaka. Powodowało to dwa problemy:

  1. Gdy ktoś zaakceptował wniosek zwierzaka, a w tym samym momencie jego dane uległy zmianie to dana osoba mogła nieświadomie potwierdzić coś błędnego.
  2. W jednej chwili, gdy zwierzak był usuwany z bazy, a jego wniosek był akceptowany to użytkownik mógł dostać informację zwrotną o prawidłowo wykonanej operacji chociaż wynik mógł być przeciwny.

Oczywiście sytuację można by naprawić jakimiś eventami kompensującymi albo po prostu zaakceptować ten fakt z racji rzadkości występowania. Jednak mi to nie dawało spokoju i postanowiłem to jakoś uprościć. Dodałem po prostu akcję potwierdzającą koniec edycji danego zwierzaka. Jej wykonanie jest równoznaczne z brakiem możliwości dokonania jakichkolwiek zmian po stronie modułu catalogue. Powyższe problemy odeszły, ponieważ teraz komunikacja następuje tutaj tylko w jedną stronę.

Dodanie kolejnego modułu

Po głębszym zastanowieniu doszedłem do wniosku, że w kodzie wyodrębnia się nowy koncept jakim jest samo schronisko. To ono ma w sobie informacje o limicie dostępnego miejsca, progu bezpieczeństwa oraz liczbie zaakceptowanych wniosków. Tutaj będą trzymane wszelkie ograniczenia biznesowe związane z przyjmowaniem zwierzaków do schroniska. Jest to również podejście przyszłościowe pod kątem rozwoju, czyli dodawania kolejnych placówek opiekuńczych do systemu.

Komunikacja pomiędzy modułami
Komunikacja pomiędzy modułami

System wygląda tak jak na powyższym obrazku. W tym momencie istnieje CRUD przyjmujący dane zwierzaków. Gdy je zaakceptujemy to tworzone są nowe wnioski w centralnym repozytorium, naszym Single Source of Truth. Następnie moduł shelter będzie trzymał informację o tym ile zwierzaków jest w danym schronisku. Jego zadanie to pilnowanie, aby nie zostały złamane żadne niezmienniki związane z miejscem.

Akceptacja wniosku

Wpadłem na pomysł, że schronisko będzie akceptowało wniosek “w ciemno”. Oznacza to, że gdy przejdą reguły walidacyjne związane z limitami to przypiszemy zwierzaka do placówki bez weryfikacji czy on istnieje. Następnie pójdzie informacja o tym fakcie do modułu proposal. Dopiero on potwierdzi w 100% czy wniosek znajduje się w systemie i jest wolny. Jeśli operacja się nie powiedzie to schronisko zostanie o tym poinformowane i anuluje swoją operacje. W planach jest również zaimplementowanie wysyłki maila do użytkownika o rezultacie wykonanego przez niego działania.

Na ten moment prawidłowa rejestracja nie ma dodanej żadnej zwrotki dla osoby zainteresowanej. Po prostu następuje zmiana statusu dla wniosku i tyle. Tutaj warto byłoby również zaprogramować poinformowanie użytkownika oraz schroniska o sukcesie operacji.

Z powyższych powodów powstała dwustronna komunikacja pomiędzy proposal i catalogue. Pozwalam sobie na nią, bo interakcja proposal z shelter następuje tylko w odpowiedzi na zdarzenie dotyczące akceptacji wniosku. Repozytorium wniosków decyduje czy dany wniosek może zmienić status. Komunikuje swoją decyzję zainteresowanym modułom przy pomocy eventów tylko w przypadku zakończenia przetwarzania wniosku.

Organizacja kodu

Teraz mogę powiedzieć, że praktycznie w 100% wzorowałem się na kodzie z repozytorium ddd-by-example . Podczas pisania drugiego wpisu miałem mylne spojrzenie na opisane tam koncepty. Stąd powstał podział na trzy moduły oraz na cztery warstwy w kodzie: model, application, infrastructure oraz web.

Komunikacja pomiędzy warstwami
Komunikacja pomiędzy warstwami

Z rysunku powyżej można zaobserwować, że dolna warstwa jest potencjałem dla wyższej.

  • web korzysta z API wystawionego przez application
  • application podbiera dane przy pomocy infrastructure i modyfikuje je dzięki warstwie model
  • infrastructure przygotowuje instancje klas z warstwy model
  • model zawiera w sobie kod biznesowy

Warstwa model weryfikuje decyzje biznesowe, a rezultat jej działania jest zapisywany przez infrastrukturę. Z kolei pakiet application jest dyrygentem, który składa w sobie dane wymaganie biznesowe z dostępnych klocków. Warstwa web istnieje tylko po to, aby aplikacja mogła nosić nazwę aplikacji webowej. To tutaj odbywa się komunikacja z użytkownikiem przez protokół HTTP. Oczywiście można tą warstwę zamienić na inne UI takie jak np. konsolę czy program okienkowy.

Schemat przebiegu wykonania żądania użytkownika
Schemat przebiegu wykonania żądania użytkownika

Przykładowa organizacja kodu modułu mogłaby wyglądać tak jak poniżej. Ciężko nie zauważyć, że praktycznie wszędzie mamy zielone kłódki oznaczające, że dana klasa jest publiczna. Kiedyś pomyślałbym, że jest to niedopuszczalne. Teraz uważam, że to zależy od kontekstu i doświadczenia programistów. Można wykorzystać istniejące mechanizmy, które pilnują aby warstwy się ze sobą nie wymieszały - moduły Javy , moduły Mavena albo biblioteka ArchUnit (tutaj nie mamy ochrony w czasie kompilacji).

Wgląd w strukturę organizacji kodu
Wgląd w strukturę organizacji kodu

Podsumowanie

Włożyłem sporo wysiłku w powyżej opisane zmiany w AnimalShelter. Muszę przyznać, że w końcu jestem bardzo zadowolony z finalnego rezultatu. Zobaczymy jednak jak to wpłynie na dalsze programowanie założonych wymagań biznesowych. Oczywiście jestem otwarty na wszelkie uwagi dotyczące mojego rozumowania w sprawie tej aplikacji. Jeśli masz jakieś pomysły to proszę, podziel się nimi w komentarzu.

Link do GitHub: https://github.com/cezarysanecki/animal-shelter
Tag: 20220805-animalshelter-wszystko-postawione-do-gory-nogami