
Wiemy już jak zbudować projekt (Maven), połączyć go z bazą danych (SQL), wersjonować (Git), testować (JUnit) i udostępnić API (REST). W dzisiejszym odcinku „Czym jest…?” przyszła pora aby zająć się architekturą oprogramowania.
Uwaga! W tej serii zajmuję się opisowym przedstawieniem tematu, a celem jest zapoznanie z zagadnieniem w sposób luźny i zrozumiały dla nowicjusza. W związku z tym wiele elementów siłą rzeczy musi zostać pominiętych. Tych z was, którzy chcieliby się dowiedzieć więcej, zapraszam do źródeł na końcu artykułu.
Powoli, acz nieubłaganie zaczynamy się zbliżać do tematów, które przytłaczają swoim rozmachem nawet doświadczonych programistów. Taki Git dla przykładu – owszem potrafi być skomplikowany kiedy branche mnożą się jak króliki, a więcej czasu niż na commitowaniu tracimy na rozwiązywaniu konfliktów, ale…

No właśnie. Gita w sumie łatwo zrozumieć: do czego służy, jak działa, jak z niego korzystać. Można wszystko sprawdzić samemu zwyczajnie odpalając IDE i bawiąc się przyciskami na górnej belce.
Kto wie jak samemu szybko przetestować budowanie aplikacji składającej się z tysięcy klas – tak, że u spółki klienta po roku zmieni się zarząd i połowę funkcjonalności trzeba będzie wyciąć albo zastąpić, część początkowego zespołu będzie już zajęta innymi projektami, a główny architekt właśnie zwolnił się ze względu na wypalenie zawodowe i na szybko trzeba go zastąpić świeżakiem ledwo po rekrutacji?

Żeby nie było – samemu też uważam, że w tym miejscu rzucam się na głęboką wodę i pewnie lepiej bym zrobił udając, że problem nie istnieje. Ale, że do odważnych świat należy, więc lecimy z tematem!*
(*)Tak tłuste zagadnienie jak software architecture nie wejdzie bez srogiej popity, dlatego nie bierz mi tego za złe, że będę lał wodę jak brygada strażacka w poniedziałek wielkanocny. Jeśli jednak interesuje cię „samo mięcho” to możesz śmiało zjechać w dół, aż zobaczysz duży napis „CQRS”.
Aby chociaż początek był jako tako zjadliwy to zacznijmy od niewinnego pytania:
W jaki sposób osoba początkująca napisałaby swój pierwszy program?
System.out.println("Hello world!");
Jedna klasa odpalająca public static void main(), w środku samotna linijka kodu i cyk, gotowe! Ćwiczenie wykonane, można nagrodzić się dwoma odcinkami ulubionego serialu.

Dobra, idźmy dalej. Ten sam gość uczy się kodowania już parę miesięcy i obecnie potrafi napisać mały programik składający się z kilku klas, np. działającą w konsoli, na znakach ASCII, grę „Snake„. Ma tam obiekty takie jak sam wąż, jego segmenty, kropki na które poluje, plansza i oczywiście klasę odpowiedzialną ze sterowanie z klawiatury. Więcej niż 10 typów nie będzie.
Najpewniej całość znajdzie się w pakiecie głównym (tzw. root), może zrobi np. osobny dla węża i dla sterownika na wypadek jakby chciał tam kiedyś dołożyć obsługę myszką.

Mija kolejne pół roku i nasz bohater zaczyna się już czuć całkiem pewnie w obsłudze IDE. Poznał kilka podstawowych wzorców projektowych, w tym MVC i już ładnie wszystko pakietuje:
- logika idzie prosto do Modelu
- klasy odpowiedzialne za wyświetlanie treści są w Widoku
- sterowanie oczywiście w Kontrolerze
Ponadto, w każdym z tych pakietów są dodatkowe podpakiety, tak aby podobne elementy były schowane razem, np. stałe (constants) albo typy wyliczeniowe (enums).
Naiwnie myśli, że to jest już szczyt tego co architektura softu ma do zaoferowania.

Ale nie zatrzymujmy się. Kolejnym etapem jest nauka Springa, stworzenie w nim projektu i obnażanie się z nim przed każdym rekruterem jakiego napotka.
Nasz aplikant jest z siebie niezmiernie dumny, wszystko znajduje się na swoim miejscu:
- REST-owe kontrolery razem w pakiecie 'controllers’
- serwisy w 'service’, encje w 'model’
- klasy konfiguracyjne w „roocie”
Tak mu pokazywał to każdy darmowy kurs na YouTube, znaczy się – sprawdzona architektura i pewnie w każdej firmie tak robią.

Historia toczy się dalej. Nie wnikajmy w to jakim cudem, ale jakoś udało się podmiotowi tej przypowieści znaleźć robotę. Został junior developerem! Brawo!
W jaki sposób teraz projektuje swoje moduły?
W sraki! A dokładniej, parafrazując klasyka: „Panie Areczku, projektowanie jest dla zarządu, a dla pana są implementacje i testy.”
Niestety w pierwszej pracy nie jest tak różowo i nasz kolega wpadł w łapy prawdziwego „Januszexu”. Początkowa ekscytacja i zapał błyskawicznie wyparowały kiedy zderzyły się z codziennością. Jego zdaniem nikt się nie przejmuje, dostaje monotonne zadania implementacji kolejnych funkcjonalności, które następnie są przeglądane i wdrażane przez niezwalnianego rockstar seniora w wybranych przez niego miejscach. Dlaczego akurat tam?

Ale spokojnie, to pierwsza robota i wciąż jakieś doświadczenie komercyjne, zatem podmiot liryczny zagryza wargę i robi co mu każą… jednocześnie dużo pracując samodzielnie, po godzinach. A pogłębiająca się z miesiąca na miesiąc niechęć do szefa jest dla niego najlepszą motywacją.
Mija rok.. drugi.. piąty… Bohater dojrzewa. W miejscach gdzie do tej pory go nie było, pojawia się zagadkowe owłosienie. Architektura, która przedtem wydawała się nudna i bez sensu, nagle zaczyna podniecać, ekscytować. Czasami, kiedy jest zupełnie sam, oddaje się prototypowaniu. Ma do tego różne zabawki no code, ale wszystko trzyma w tajemnicy. To wciąż wstydliwy proceder, nie chciałby żeby jego koledzy się o tym dowiedzieli. Żartom nie byłoby końca.
Po żółtodziobie, którego poznaliśmy na początku tej historyjki ostało się już tylko mało wykwintne poczucie humoru. Brzydkie kaczątko transformowało we w pełni upierzonego fullstackowego łabędzia znającego kilka języków oprogramowania i mającego w rękawie sztuczki na każdą okazję.
Z pierwszej roboty są już tylko blaknące wspomnienia, potem była druga i trzecia – ta obecna. Tutaj ma nadzieję zostać na dłużej – polubił swój zespół, szef też jest spoko i świetnie się dogadują. A co najlepsze – bierze aktywny udział w budowie nowych projektów. Planuje, pomaga, rozmawia z klientami. Niedawno został mianowany team leadem – może trochę przedwcześnie, ale i tak da z siebie wszystko, żeby nie zawieść kolegów i potwierdzić swoją wartość.

A co z projektowaniem?
Jako lider odpowiedzialny za to aby dowieźć projekt, a jednocześnie jako pracownik, który nie jest w swojej firmie jedynie „przelotem”, nasz bohater dba przede wszystkim o czytelność i elastyczność kodu. Świetnie pomaga mu w tym architektura heksagonalna w parze z
CQRS
Command Query Responsibility Segregation
Polecenie. Zapytanie. Odpowiedzialność. Rozdzielenie. Czyli wzorzec, który rozdziela instrukcje idące w stronę bazy danych na te, które mają coś tam zmienić („Polecenie”, np. dodać albo usunąć użytkownika) oraz te, których jedynym zadaniem jest zwrócenie jakiejś wartości znajdującej się w bazie („Zapytanie”, np. „znajdź użytkowników o statusie VIP”).
Co nam to daje? Przede wszystkim opytmalizację – teraz możemy mieć dwie bazy danych: szybką do odczytu (bo większość operacji na bazie to właśnie odczyt) oraz wolniejszą do zapisu. Nic nie stoi na przeszkodzie aby to były DBMS-y tego samego typu, albo np. jedno SQL a drugie NoSQL. Ta sztuczka również sprawia, że jeśli np. wyszukiwanie rekordów działa wzorowo, ale zapisywanie wywala czasami błędy to mocno zawęża się nam spektrum poszukiwań buga.

Ale to nie wszystko. Wcześniej padło również pojęcie:
Architektura Heksagonalna
Nazwa wzięła się od grafiki obrazującej jak wygląda to w praktyce – jak łączy poszczególne komponenty/moduły aplikacji ze sobą. Inny popularny przydomek to „architektura portów i adapterów”, co znacznie lepiej tłumaczy działanie tego wzorca.

Kluczem do ogarnięcia działania architektury heksagonalnej jest zrozumienie potrzeb będących powodem jej używania:
- Hermetyzacja
- Ułatwienie testowania
- Odroczenie implementacji
Jak widzimy na powyższym obrazku, warstwa logiki (zwana również warstwą biznesową czyli clue naszej appki) jest odizolowana zarówno od dostawców jak i odbiorców – ergo: NIE ZALEŻY OD NICH. Jak zepsuje się baza danych to nic nie musimy poprawiać w logice, jak REST ma pomieszane metody to problemu szukamy właśnie tam, a nie w samym centrum. Hermetyzacja.
Załóżmy, że chcemy przetestować zapisywanie wyników do bazy danych. Jeśli korzystamy bezpośrednio z niej to będziemy musieli sięgnąć po testy integracyjne, które są trudniejsze do zakodowania i zazwyczaj trwają dłużej. No i jest ryzyko, że coś zepsujemy w samej bazie. Tymczasem w omawianym wypadku tworzymy sobie na szybko tzw.
„inMemoryRepository”
które jest niczym innym jak klasą implementującą interfejs-port i zawierającą zwykłą javową mapkę (np. HashMap albo ConcurrentHashMap). To na tym przeprowadzamy wszystkie testy jednostkowe. Szybko i łatwo.

Odroczenie implementacji to fantastyczna sprawa. Wyobraźcie sobie, że dostaliście do napisania jakiś program i macie na to rok czasu. Wybieracie odpowiednią technologię i jedziecie z koksem. Zbliża się deadline, ale program jest w zasadzie gotowy, pozostały tylko testy. Można się wyluzować i poczytać co tam nowego w świecie technologii się wydarzyło przez ostatnie miesiące.
Ku waszemu zaskoczeniu okazało się, że niedawno zadebiutowało nowe rozwiązanie – nie dość, że pasuje do waszego projektu, to jeszcze działa szybciej i jest bardziej stabilne. No, ale cóż – już połączyliście wszystko co się dało z poprzednim stackiem i nie ma sensu tego wszystkiego przepisywać.
W tym miejscu bardzo żałujecie, że nie odroczyliście implementacji. Można było zwyczajnie poustawiać odpowiednio porty-interfejsy, a teraz wystarczyłoby tylko przestawić jedną wajchę aby skorzystać z nowego rozwiązania. Działa to trochę na zasadzie wzorca „Strategia„.

Mijają kolejne lata. Nasz bohater cały czas zdobywa doświadczenie, projektując, nadzorując, mentorując, testując i – rzecz jasna – programując. Powoli zaczyna go to nużyć. To co wcześniej było nowe i inspirujące, teraz stało się żmudne i powtarzalne. Bierze się za różne programy, w różnych językach, dołącza do różnych zespołów, ale procedury są zawsze takie same.
Po co zmieniać coś co działa?
The end?
Historia podmiotu lirycznego na dzisiaj dobiega końca, ale będzie kontynuowana w kolejnym odcinku. Bez obaw:
Każdy zasługuje na swój happy end.
Początkowo (jak zwykle…) chciałem w jednym wpisie zamieścić zarówno coś o CQRS i architekturze heksagonalnej, jak i o koncepcie DDD: Domain Driven Design, ale sami widzicie do jakich rozmiarów tekst doszedł w tym miejscu. A to przecież nie ma być kompletny poradnik tylko luźna prezentacja tematu dla nowicjuszy – takie było wstępne założenie całego cyklu.
Z tego powodu zrobimy sobie teraz przerwę, a tych z was, których temat architektury oprogramowania zaciekawił – odsyłam do poniższych źródeł skąd samemu czerpałem wiedzę do niniejszego artykułu.
Widzimy się już za kilka dni w tekście pod tytułem „Czym jest Domain Driven Design (DDD)?”.
Źródła:
Architektura portów i adapterów POL
Cykl wpisów poświęconych architekturze oprogramowania ANG
Kurs architektury oprogramowania na Udemy POL (płatne)
Podcast Better Software Design, odcinek o CQRS POL
Podcast Better Software Design, odcinek o architekturze heksagonalnej POL