Co by się stało, gdyby dzisiaj przyszedł do Ciebie biznes i powiedziałby, żeby w rozwijanej aplikacji zrezygnować z głównego frameworka (jakim jest np. Spring) i zastąpić go innym? Wiem, że to mało prawdopodobny scenariusz, ale jednak istnieje ryzyko, że mogłoby się tak wydarzyć. Mi od razu przed oczami przeleciałyby wszystkie adnotacje znajdujące się nad każdą klasą, repozytoria tworzone magicznie z interfejsów czy wstrzykiwane zależności przez kontekst Springa. I to pewnie nie byłby początek wszystkich zmian, które musiałbym wprowadzić w kodzie.
No właśnie, frameworki niespostrzeżenie wbijają się w głąb naszego kodu i nim się obejrzymy ciężko je potem stamtąd wygonić. Dla przykładu pisanie testów może wymagać postawienia całego kontekstu aplikacji, kiedy tak naprawdę trzeba sprawdzić tylko jeden przypadek użycia. Jeżeli jednak uda się stworzyć oprogramowanie bez uzależnienia od szkieletu zewnętrznej aplikacji to kod staje się czytelniejszy. Zwiększana jest również świadomość w zespole w jaki sposób działa system (nie dzieje się tak dużo magii). W tym właśnie może nam pomóc architektura heksagonalna, nazywana inaczej Ports & Adapters.
Czym wyróżnia się architektura heksagonalna?
W 2005 roku Alistair Cockburn przedstawił koncepcję, w której to rdzeń aplikacji pozostałby oddzielony od jakichkolwiek zewnętrznych zależności. Kod infrastruktury byłby odseparowany od kodu biznesowego i na odwrót. Oczywiście taki efekt można osiągnąć dzięki Dependency Injection. Wszędzie tam gdzie ma być odwołanie do zewnętrznej zależności zastosowanie powinien znaleźć interfejs. W ten właśnie sposób powstaje port, który będzie miał swoją implementację w postaci adaptera. Nasza domena nie musi o tym wiedzieć, ona tylko udostępnia wejście (primary/driving ports) i wyjście (secondary/driven ports) do swojej logiki biznesowej.
Jeżeli wystąpi przypadek użycia, w którym logika biznesowa musi skorzystać z bazy danych to wywołuje tylko odpowiednią metodę interfejsu. Nie przejmuje się tym jaki będzie użyty silnik bazodanowy w adapterze. Natomiast, gdy klient zewnętrzny chce skorzystać z konkretnego przypadku użycia domeny to także wykorzystuje przygotowaną metodę portu wejściowego.
Za i przeciw
Ogromnym plusem przemawiającym za architekturą heksagonalną jest czysta implementacja domeny, która jest niezależna od zewnętrznych rozwiązań. W ten sposób zyskujemy dowolność podmiany adapterów bez martwienia się o przypadkowe zepsucie obsługiwanych przypadków biznesowych. Bez problemu możemy też dodać kolejny środek komunikacji z naszą aplikacją poprzez np. REST, konsolę czy brokera wiadomości. Ułatwieniem jest sposób w jaki możemy testować naszą domenę. Dzięki luźnym powiązaniom bez problemu możemy tworzyć zaślepki i poddawać weryfikacji tylko wybraną część aplikacji.
Ports & Adapters opiera się na dużej abstrakcyjności co zwiększa złożoność systemu. Praktycznie wszędzie mamy do czynienia z interfejsami. Jakakolwiek zmiana w nich powoduje duży narzut implementacyjny w wielu klasach. Sprawia to, że architektura heksagonalna jest polecana tylko w przypadku, gdy zespół deweloperski składa się z członków o wysokich kompetencjach. Jeżeli zaakceptuje się te niedogodności to rozwijana aplikacja na pewno zwróci włożony czas z nawiązką!
Podsumowanie
Dobrą informacją jest fakt, że można ze sobą łączyć różne koncepcje. Stosując architekturę sterowaną DDD możemy w wybranych Bounded Context’ach zastosować architekturę Ports & Adapters jeżeli wymagana jest w nich wysoka testowalność. Sam jestem zwolennikiem uniezależniania się od wybranego frameworka z tego powodu ten styl tworzenia aplikacji naprawdę mi odpowiada. Zawsze wolę mieć wybór niż go nie mieć. Domyślam się jednak, że ciężko jest utrzymywać taki kod, aby nie popełnić w nim błędu, który zaprzepaści nasze starania o czystej architekturze. Dlatego właśnie trzeba poświęcić sporo czasu, żeby zwiększyć swoje kompetencje w tym zakresie. A teraz żegnam się z Wami i widzimy się w następnym artykule, gdzie pogadamy o mikroserwisach.
Na razie i cześć!