To już dziesiąty wpis dotyczący przepisania swojej starej aplikacji na nowe rozwiązanie! W związku z tym przyszła pora na małe podsumowanie dotychczasowych prac. Sprawdzimy co udało się już zrobić, co jeszcze zostało i czy planuję coś dorzucić ekstra. Przy okazji przedstawię rozwiązanie jakie zastosowałem do wysyłki maili przy adopcji wybranego pupila oraz jak zmienić commit message przy merge.

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

Co udało się zrobić, a co w planach?

Na tą chwilę w moim projekcie istnieje możliwość dodania zwierzaka do schroniska, edycji jego danych, usunięcia w przypadku błędnego dodania oraz adopcji. W przypadku akceptacji pupila istnieją dwie reguły.

  • przekroczenie progu bezpieczeństwa wprowadza sytuację wyjątkową
  • nie ma możliwości posiadania większej ilości pupili niż liczba miejsc w schronisku

Wszystkie te procesy są udostępnione na świat zewnętrzny przy pomocy dedykowanych endpointów jak również dwa możliwe zapytania o wszystkie zwierzaki albo jeden konkretny. Przy okazji adopcji zwierzaka dodana jest jeszcze funkcjonalność wysyłki maili do wszystkich opiekunów schroniska.

Oczywiście wykorzystałem w mojej aplikacji CQRS, aby oddzielić zapis od odczytu. Cały projekt podzieliłem też na dwa moduły Mavena, aby logika biznesowa była wolna od jakichkolwiek zewnętrznych rozwiązań. Wydaje mi się, że to wszystko to kawał dobrej roboty i sporo zdobytego doświadczenia. Jednak na tym nie poprzestaję i mam jeszcze kilka rzeczy do zaimplementowania, aby całkowicie odzwierciedlić starą aplikację.

Aktualny stan rzeczy na GitHub
Aktualny stan rzeczy na GitHub

Lista jest spora, ale lepiej widzieć dokąd się zmierza. Nauka planowania to także ważny aspekt pracy programisty, więc cieszę się, że i tutaj udało mi się popracować nad sobą. Wszystko do tej pory trzymałem w swojej głowie, ale łatwo w ten sposób przeoczyć niektóre rzeczy, gdy ma się gorszy dzień. Niemniej jednak nie zatrzymuję się i lecę dalej z tematem.

Jak wygląda mechanizm wysyłania maili?

Przy okazji chciałbym przedstawić w jaki sposób zaimplementowałem wysyłkę maili. Ogólnie idea jest taka, aby stworzony moduł notyfikacji przechowywał w swojej bazie danych adresy e-mail opiekunów schroniska. Oczywiście nie będzie on odpowiedzialny za ich pobieranie tylko będzie je uzyskiwał w postaci zdarzenia z innego źródła. Co za tym idzie, niezbędne będzie zaimplementowanie modułu do rejestracji opiekunów wraz z emisją takich zdarzeń.

Moduł notyfikacji ma własny zestaw danych niezbędny do wysyłki maili
Moduł notyfikacji ma własny zestaw danych niezbędny do wysyłki maili

W ten sposób jeśli przyjdzie nam wyodrębnić kiedyś mikroserwisy na podstawie zdefiniowanych modułów to będzie to o wiele prostsze. Niby jest replikacja danych, ale te dwa konteksty nie będą od siebie zależne co daje pożądaną elastyczność w tej architekturze.

Jeśli chodzi o kwestię implementacji to stworzyłem klasę HandleSuccessfulAdoption obsługująca zdarzenie o prawidłowej adopcji. Następnie ona pobiera dane teleadresowe opiekunów i wysyła do nich wiadomość w postaci interfejsu Notification. Na tą chwilę jest tylko jeden rekord go implementujący o nazwie SuccessfulAdoptionNotification.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Notification {

  enum NotificationType {
    Adoption
  }

  NotificationType type();

  record SuccessfulAdoptionNotification(UUID animalId, String animalName,
                      Integer animalAge, String animalSpecies) implements Notification {

    @Override
    public NotificationType type() {
      return NotificationType.Adoption;
    }
  }
}

Starałem się stworzyć elastyczne rozwiązanie pozwalające wysyłać informację o danym zdarzeniu za pomocą różnych środków komunikacji, nie tylko maila. Stąd powstał interfejs Notifier, który przechowywany jest w postaci zbioru w HandleSuccessfulAdoption. Teraz wystarczy go tylko zaimplementować np. dla powiadomień SMS, zarejestrować w kontenerze Springa i wszystko zadzieje się automatycznie.

Aktualne działanie przedstawia się następująco. Kiedy pupil zostanie adoptowany, informacja o tym zdarzeniu zapisuje się w bazie, a następnie jest wysyłana do modułu notyfikacji. Tam pobierane są dane opiekunów schroniska i do każdego z nich wysyłany jest komunikat o zaistniałej sytuacji. Do tworzenia kontentu emaila wykorzystałem bibliotekę Thymeleaf, która w łatwy sposób pozwala wypełnić treść szablonu HTML.

Schemat wysyłki maili do opiekunów schroniska w przypadku adopcji zwięrzaka
Schemat wysyłki maili do opiekunów schroniska w przypadku adopcji zwięrzaka

Rozwiązanie z wysyłką ma pewne wady…

Główną wadą jest fakt, że wysyłka maili dzieje się synchronicznie przez co użytkownik podczas adopcji musi niepotrzebnie czekać aż wszystkie wiadomości trafią do zdefiniowanych odbiorców. Jest to niekomfortowe z punktu widzenia user experience z czego zdaje sobie sprawę. Z tego powodu będę musiał przyjrzeć się tej części oprogramowania i zmienić ten mechanizm.

Drugim mankamentem jest fakt, że w sytuacji, gdy nie uda się wysłać maila to informacja o tym jest bezpowrotnie tracona. Zwierzak co prawda zostanie zaadoptowany, bo nie chcemy uzależniać adopcji od wysyłki maili, jednak zależy mi na tym, aby ta wiadomość jednak trafiła do adresatów. Na tą chwilę jest to rozwiązanie at most one, które będę się starał zastąpić podejściem exactly once. Moim pomysłem jest, aby zapisywać informacji o mailu w bazie, a następnie dzięki adnotacji @Scheduled próbować je wysłać do opiekunów co jakiś ustalony czas. Będzie to przejściowy etap, at least once, ponieważ trzeba jeszcze przemyśleć jak moduł notyfikacji ma sprawdzać czy dana wiadomość nie została już wcześniej obsłużona.

Jeśli starczy chęci i zapału to przechodząc na architekturę mikroserwisową postaram się w swoim rozwiązaniu wykorzystać Kafkę. Chciałbym sprawdzić z jakimi problemami trzeba się zmierzyć przy jej wykorzystaniu. Warto dodać, że jest to dosyć pożądane narzędzie przez firmy obecne na rynku pracy. Z tego powodu dobrze jest rozszerzyć swoje kompetencje w tym kierunku, zwłaszcza w przypadku, gdy nie jest to możliwe u obecnego pracodawcy.

Problem ze zmianą nazw commitów dla merge

Przy okazji chciałbym się podzielić problemem z jakim musiałem się zmierzyć podczas zmiany nazw commitów, które zostały automatycznie wygenerowane przy merge no fast forward. Oczywiście można to zrobić podczas merge, ale łatwo o tym zapomnieć. Co jednak zrobić, gdy już dorzuciliśmy kilka takich commit merge do mastera?

Zażegnana sytuacja z nazwami commitów
Zażegnana sytuacja z nazwami commitów (nazwy nie są idealne)

Gdy wywołamy git rebase -i HEAD~n na master to dostaniemy dostęp tylko do commitów znajdujących się na domergowanych branchach. To nie do końca o to nam chodziło. Jednak wystarczy dodać jeszcze jedną flagę -r (--rebase-merges) i ponownie wywołać wcześniej przedstawioną komendę. Teraz sytuacja jest zgoła odmienna. Commity znajdują się w grupach swoich branchy, a my natrafiamy na linijkę o przykładowej treści merge -C 401f255 Finished-task-2 # Finished task #2. Wystarczy, że zmienimy flagę z -C na -c i voila!, otrzymamy dostęp do zmiany wiadomości tego merge commita.

Podsumowanie

W poprzednich wpisach pisałem sporo o tym co chcę zrobić i mało skupiałem się, aby dowieźć chociaż to co sobie założyłem na samym początku, czyli po prostu przepisać starą aplikację w nowy sposób. Teraz, gdy zdefiniowałem sobie mniejsze zadania na GitHub mogę się po prostu skupić na ich wykonywaniu i skończyć chociaż to wymagane minimum. Mam nadzieję, że teraz mi się to uda!