Czas powrócić do opisywania prac nad aplikacją AnimalShelter! Niestety ostatnimi czasy nic o niej nie pisałem na blogu, ponieważ zagubiłem się w rozważaniach nad implementacją. Zastanawiałem się jak zrobić elastyczne rozwiązanie, aby nie uzależnić domeny biznesowej od zewnętrznych narzędzi. Chciałem, aby ten mój model był tak bardzo… CZYSTY! Jednak w niektórych momentach znowu zapomniałem, aby trzeba iść z rozwiązaniem na przód. Wpadłem w pułapkę poszukiwania “rozwiązania idealnego”. Mam nadzieję, że wyrzucenie wszystkich myśli na papier pomoże mi spojrzeć na to wszystko z innej perspektywy.

Lista wszystkich wpisów dotyczących projektu AnimalShelter:
#1 - Opis projektu AnimalShelter
#2 - Pierwsze kroki w backendzie
#3 - Refactoring i prace rozwojowe części serwerowej
#4 - Tworzenie GUI w Angularze
#5 - Zatrzymaj się, przemyśl i zacznij działać!
#6 - Pomysł na architekturę
#7 - Wykorzystanie CQRS
#8 - Ponowna implementacja
#9 - Rozterki architektoniczne
#10 - Podsumowanie + implementacja wysyłki maili
#11 - Programowania ciąg dalszy
#12 - Dopinanie zadań do końca

Wykorzystanie CQRS

W jednym z poprzednich artykułów pisałem, że chcę wykorzystać CQRS w swoim projekcie AnimalShelter. Utworzyłem w tym celu potrzebną do tego infrastrukturę. Jednak przysporzyło mi to wiele problemów, ponieważ uważałem, że nie może mi ona “wlać się” do mojej domeny. Było to w sumie dziwne, bo sam jeszcze nie wiedziałem jak wygląda moja domena. Na ratunek przyszedł mi artykuł Herberto na temat tego w jaki sposób połączyć ze sobą wiele ciekawych pojęć m.in. DDD, architekturę heksagonalną, CQRS. Znalazłem w nim taki o to ciekawy schemat.

Diagram obsługi zapytań do systemu korzystającego z CQRS
Diagram obsługi zapytań do systemu korzystającego z CQRS, źródło: https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together

Najważniejszą rzeczą w moim przypadku jest to, że Command Handler należy do rdzenia aplikacji. Było to dla mnie jak pstryczek w nos. Przecież to jest moja infrastruktura! Muszę skorzystać z zewnętrznego lub własnego rozwiązania, aby w jakiś sposób odbierać dane z żądań. Z tego powodu klasy implementujące stworzony przeze mnie generyczny interfejs CommandHandler bez problemu mogą znaleźć się aplikacji a nie będą w domenie. Bo przecież kolejną warstwą są serwisy aplikacyjne, które można pominąć i dopiero wtedy napotykamy naszą domenę w postaci serwisów czy encji. W ten o to sposób przestałem się martwić o przenikanie szkieletu CQRS do trzewi aplikacji. Jednak powstaje teraz pytanie w jak w jaki sposób walidować przychodzące dane?

Co z walidacją danych?

No właśnie co z walidacją? Czy korzystać z Hibernate Validator, czy może wystarczy tylko walidacja na poziomie Value Objects? Bo przecież nie może być ona powtórzona w tych dwóch miejscach, prawda? No nie do końca, z pomocą przychodzi nam wykład Łukasza Szydło z Boiling Frogs 2020 (dowiedziałem się o nim ze Slacka JVM Poland, na którego serdecznie zapraszam!). Łukasz na początku tłumaczy nam, że istnieją trzy poziomy walidacji.

  • Formatu
  • Struktury
  • Spójności

Walidacja formatu

W momencie, gdy przychodzi do naszej aplikacji request w postaci XML czy JSON to musi on zostać zserializowany na wewnętrzne obiekty, aby można było wykorzystać te dane. Za ten rodzaj walidacji odpowiedzialny jest nasz framework. W przypadku błędu parsowania od razu dostajemy o tym informację, a gdy się uda to przechodzimy dalej.

Walidacja struktury

Ten poziom walidacji mógłby być nawet wykonany po stronie interfejsu użytkownika, ponieważ nie są przy niej potrzebne dane spoza wysyłanego requestu. Oznacza to nic innego jak sprawdzenie czy pola, które nie mogą być nullem, faktycznie nimi nie są, czy wartości są poprawne np. większe od 0 albo pasują do wybranej maski itp. Jeżeli wszystkie dane są poprawne to można przejść do ostatniego poziomu walidacji.

Walidacja spójności

Tutaj bierzemy już pod uwagę stan naszego systemu. Pobieramy go i weryfikujemy czy request, który przyszedł, z poprawną strukturą, jest możliwy do wykonania. Sprawdzamy czy zostały spełnione wszystkie reguły biznesowe, które zdefiniowaliśmy w danym kontekście, czy jesteśmy w stanie zmienić coś w naszej aplikacji.

Moje problemy z walidacją

Najbardziej problematyczną walidacją z tych trzech wymienionych staje się dla mnie walidacja struktury. Dlaczego? Z powodu tego, że muszę ją powtórzyć. Na początku przy walidacji requestu przy użyciu wcześniej wspomnianego Hibernate Validator, a później w moich Value Objects. I to jest właśnie takie niefajne, coś co mnie nie przekonywało przez długi czas i szukałem czegoś lepszego.

Łukasz przedstawił na swojej prezentacji rozwiązanie Marcina Pokory-Jandy, które wydaje się naprawdę porządne. Niczego nie powtarzamy, wszystko jest w jednym miejscu. Jednak musimy w tym wypadku działać przeciwko naszemu frameworkowi oraz polegać na tym, że nasz zespół to ogarnie. Jest ono po prostu mniej intuicyjne niż wcześniej zaproponowane rozwiązanie, które zna prawie każdy. Dodatkowo usłyszałem jedno zdanie, które przechyliło szalę na korzyść powtórzonej walidacji: “WSZYSCY CHODZĄ NA SKRÓTY”. Z tego powodu ja też pójdę i nie będę już miał żadnych wyrzutów sumienia. Niby nic się nie zmieniło w kodzie, a jednak zmieniła się moja świadomość tego co robię i dlaczego.

Możliwe gatunki zwierząt do przyjęcia w schronisku

Ostatnia rzecz jaka nie dawała mi spokoju był sposób w jaki miałem przechowywać gatunki zwierząt, które może przyjąć schronisko. Czy powinno być to w postaci enuma, czy może trzymane w pliku, a może na bazie danych? Nie chciałem tego wbijać na stałe do kodu, bo to także nie wydaje się fajne. Lepiej, żeby to było na swój sposób konfigurowalne. Jednak czy tak zawsze musi być? Oczywiście to zależy. Jeżeli to schronisko od wielu lat przyjmuje tylko koty i psy to czemu nagle miało być zmienić politykę i przygarniać np. króliki. W takim wypadku jak najbardziej możemy to utworzyć jako enum i pójść dalej z implementacją.

W przypadku nowego schroniska sprawa mogłaby wyglądać zupełnie inaczej. Nie znając miejscowego zapotrzebowania właściciele schroniska chcieliby mieć możliwość dodawania i usuwania gatunków zwierząt jakie znajdują się w schronisku. Z tego powodu postanowiłem, że nie będę się dłużej tym przejmował i zaimplementuje to rozwiązanie w taki sposób, aby korzystało z bazy danych. Oczywiście przy wykorzystaniu abstrakcji jaką jest interfejs.

Podsumowanie

Nie będę ukrywał, że w trakcie pisania tego artykułu zacząłem umieszczać te wszystkie uwagi w kodzie. Jednak nimi pochwalę się dopiero w następnej części tej serii artykułów. Jeżeli również spotkałeś się z tego typu rozdarciami wewnętrznymi i znalazłeś rozwiązania tych problemu to podziel się nimi w komentarzu. Z chęcią się o nich dowiem!