Architektura Hexagonalna znana też jako Onion Architecture lub Ports & Adapters została wymyślona przez Alistaira Cockburna i opublikowana w 2005 roku. Jej celem jest uniknięcie znanych pułapek strukturalnych w OOP jak np. zależności między wartswami czy też wplatanie kodu odpowiedzialnego za GUI do warstwy logiki biznesowej. Ma ona na celu stworzenie luźno powiązanych komponentów aplikacji, które można łatwo połączyć za pomocą portów i adapterów. To sprawia, że komponenty są wymienialne na każdym poziomie i ułatwia automatyzację testów.
Cześć
To jest już trzeci wpis z mini-serii blogów o architekturach aplikacyjnych. W poprzednich wpisach mogłeś przeczytać o
W dzisiejszym artykule dowiesz się:
Czym jest architektura hexagonalna
Celem architektury hexagonalnej jest umożliwienie, aby aplikacja którą rozwijamy była traktowana tak samo przez użytkowników, programy zewnętrzne, testy czy też skrypty. Dodatkowo ma ona umożliwiać rozwój i testowanie aplikacji w oderwaniu od zewnętrznych zależności (baza danych / zewnętrzne API).
Architektura hexagonalna w centrum aplikacji stawia na przypadki użycia i domenę – to też jest powód dlaczego sprawdza się ona dobrze z DDD. Warto zauważyć, że ten styl architektoniczny jest przeciwny architekturze wartstwowej – tutaj zależności są kierowane do środka domeny a nie jak w przypadku warstw od góry do dołu.
Nazwa architektury wzięła się z efektu wizualnego jaki przedstawia hexagon – więcej o tym przeczytasz w artykule Alistair’a Cockburn’a, twórcy architektury:
The hexagon is not a hexagon because the number six is important, but rather to allow the people doing the drawing to have room to insert ports and adapters as they need, not being constrained by a one-dimensional layered drawing. The term ‘’hexagonal architecture’’ comes from this visual effect.
Jak wygląda struktura hexagonu
Hexagon składa się z trzech kluczowych elementów: domeny (core), portów i adapterów. Jeżeli spojrzymy na hexagon zaprezentowany na rysunku powyżej można też zauważyć, że nasz core (domena i przypadki użycia) używają portów – czyli domena wie na co dany port pozwala, np. zapis użytkownika ale nie wie jak to się stanie. I do tego właśnie służą adaptery. Aplikacja może mieć np. prawdziwą bazę danych, która rzeczywiście zapisze użytkownika w bazie ale może to też być hashmapa, która po restarcie aplikacji nie będzie już niczego pamiętać.
Porty dzielą się na wejściowe i wyjściowe. Wejściowe to te, które sterują naszą logiką np. GUI, gdzie użytkownik wywołuję daną akcję. Wyjściowe są używane (sterowane) przez logikę biznesową. Tutaj najlepszym przykładem są bazy danych i zewnętrzne API. Aplikacja musi porozumieć się z jakimiś zewnętrznymi komponentami i w tym celu używa właśnie portów wyjściowych.
Domena
Są to nasze obiekty domenowe. Ich implementacja powinna być na tyle jasna, żeby nawet ktoś nie techniczny mógł podejść i powiedzieć ok, rozumiem co tu się dzieje. Obiekty domenowe nie powinny mieć żadnych zewnętrznych zależności. Domena opisuje jak coś zrobić.
Przypadki użycia
To klasy, które obsługują konkretne problemy biznesowe. Jest to faktyzcna wartość dla biznesu, która rozwiązuje ich problem. Przypadki użycia mogą zawierać wszystkie niezbędne walidacje i logikę reguł biznesowych, które są specyficzne dla konkretnego przypadku użycia (dlatego też nie mogą być implementowane w obiektach domeny) a następnie delegują pracę do domeny. Use case’y nie są zależne od zewnętrznych komponentów, natomiast jeżeli potrzebują czegoś z zewnątrz (np. pobranie użytkownika) to tworzą do tego port (albo używają istniejącego). Przypadki użycia mówią co zrobić.
Porty wejściowe i wyjściowe
Porty możesz sobie wyobrażać dokładnie tak jak porty w komputerze, np. USB. Nie jest ważne czy podłączysz urządzenie firmy X czy Y – oba powinny działać.
W architekturze hexagonalnej komunikacja do i z aplikacji odbywa się przez porty. Port wejściowy będzie zazwyczaj interfejsem, który jest implementowany przez konkretny przypadek użycia. Podobnie jest z portem wyjściowym – to również będzie interfejs. Tym razem jednak przypadek użycia będzie go używał – a nie tak jak w przypadku portu wejściowego implementował.
Dzięki portom wejściowym i wyjściowym mamy bardzo wyraźne miejsca, w których dane wchodzą i wychodzą z naszego systemu, ułatwiając rozumowanie na temat architektury.
Adaptery
Adaptery tworzą zewnętrzną warstwę architektury hexagonalnej. Nie są częścią rdzenia, ale wchodzą z nim w interakcję. Adaptery również dzielimy na dwie grupy: wejściowe i wyjściowe. Wejściowe (lub sterujące) wywołują porty wejściowe, aby coś zrobić w aplikacji – w końcu wywołują konkretny przypadek użycia. Wyjściowe (sterowane) są natomiast wywoływane przez przypadki użycia.
Adaptery ułatwiają wymianę określonej warstwy aplikacji, np. zmiana bazy danych na inną. Wystarczy napisać tylko nowy adapter.
Wady i zalety architektury
Zalety:
- testowalność
- rozwijalność i utrzymanie
- wymiana technologii
Wady:
- trudniejsza nawigacja po kodzie źródłowym – musimy ogarniać co, gdzie jest używane
- wiele adapterów = wiele testów
Podsumowanie
W tym krótkim wpisie chciałem przedstawić Ci ideę architektury hexagonalnej. Jeżeli czytałeś wcześniejsze artykuły to zapewne już wiesz, że użycie konkretnej architektury zależy od tego czego chce biznes. W przypadku architektury hexagonalnej dobrym wyborem jej zastosowania wydają się być projekty o zmiennej i złożonej logice biznesowej. Natomiast zastosowanie jej w CRUD-ach będzie zdecydowanie przesadą i przerostem formy nad treścią. Zamiast uprościć zrozumienie, możesz je tylko niepotrzebnie skomplikować.
Źródła:
- Hexagonal architecture
- Hexagonal Architecture with Java and Spring
- Hexagonal Architecture: three principles and an implementation example
- Build Maintainable Systems With Hexagonal Architecture
- Hexagonal Architecture by example – a hands-on introduction
- ITT 2018 – Jakub Nabrdalik – Hexagonal Architecture in practice
Za tydzień
Moja lista todo robi się coraz dłuższa… jednak zanim zacznę ją czyścić (może powinienem pisać 2 artykuły tygodniowo) chciałbym dokończyć serię o architekturach aplikacyjnych. Dlatego też za tydzień opiszemy popularną architekturę mikroserwisów.
no raczej jest właśnie łatwiej
te adaptery to dostarczone przez ich producentów elementy środowiska, tego używa a nie testuje
yo, fajny artykuł, mam pytanie odnośnie fragmentu tekstu ” Przypadki użyciaTo klasy, które obsługują konkretne problemy biznesowe. Jest to faktyzcna wartość dla biznesu, która rozwiązuje ich problem. Przypadki użycia mogą zawierać wszystkie niezbędne walidacje i logikę reguł biznesowych, które są specyficzne dla konkretnego przypadku użycia (dlatego też nie mogą być implementowane w obiektach domeny) a następnie delegują pracę do domeny.” nie rozumiem do końca podziału przypadków użycia i domeny w tym opisie rozumiem to tak, że przypadki użycia są wystawionym API z modułu(opisanym jako port wejściowy w Twoim artykule) i one bezpośrednio kierują ruch do domeny, która posiada walidację i… Czytaj więcej »
przypadki użycia to nie klasy a scenariusze
Cześć!
Dzięki za wyjaśnienie architektury Ports & Adapters. Koncepcja nie wydaje się trudna i daje sporo możliwości, ale pewnie trzeba spędzić z nią trochę czasu, aby znaleźć jej ciemne strony 😉 Wykorzystywałeś ją może w jakimś projekcie produkcyjnym?
Pozdrawiam
Hej, niestety nie miałem jeszcze okazji wykorzystania jej na produkcji.
Gdybyś szukał linków jak używać/pisać kod w tej architekturze, to te linki mogą być pomocne: