Architektura wspierająca podejście Domain First
Na poziomie aplikacji architektura powinna wspierać modelowanie domeny czyli serca całego systemu. Czym powinna się charakteryzować architektura, żeby wspierać podejście Domain First? Czy tradycyjne warstwy są dobrą odpowiedzią na te potrzeby?
Warstwy
Rozpoczęcie pracy nad systemem modelowania reguł i procesów biznesowych wymaga odpowiednich środków. Skoro to nie wybory technologiczne mają zostać podjęte jako pierwsze, to musimy odpowiednio odseparować technologię od naszej domeny, tak aby móc ją rozwijać niezależnie.
Separacji różnego rodzaju kodu w aplikacji służą zwykle warstwy. Bardzo często architektura aplikacji jest wręcz utożsamiana z podziałem na warstwy. Wokół tego tematu krąży wiele nieporozumień, które niestety często przekładają się na karykaturalną implementację warstw, lub całkowite zanegowanie ich przydatności. Ponadto obok tradycyjnej architektury warstwowej pojawiły się inne jej odmiany takie jak Hexagonal / Onion / Clean Architecture. O co w tym wszystkim chodzi?
Jak zawsze w wyjaśnieniu pomaga nam powrót do pryncypiów, zasad, z których wyrosły architektury warstwowe. Dlaczego w ogóle potrzebny jest podział na warstwy? Co one nam dają? Kiedy są przydatne, a kiedy nie?
Warstwy służą separacji pewnych obszarów systemu. Separacja ta jest nam potrzebna z kilku powodów. Po pierwsze chcemy oddzielić od siebie obszary, które zmieniają się z różnych powodów, tak żeby zakres zmian był możliwie mały. Po drugie chcemy oddzielić to co istotne od tego co drugorzędne w taki sposób, żeby to co najistotniejsze było najbardziej niezależne. Po trzecie chcemy określić granice, przez które nie będą przeciekały koncepcje charakterystyczne dla danej warstwy. Ostatecznym celem jest system, w którym poszczególne obszary są spójne wewnętrznie i możliwie mało zależne od innych obszarów. Dlaczego takie systemy są preferowane? Dlatego, że są znacznie łatwiejsze w utrzymaniu i rozwoju, a to jest zawsze najdroższa część życia każdego systemu.
Jeżeli widzimy, że na każdej z granic między warstwami obiekty znane po jednej stronie przepakowywane są na niemal identyczne obiekty znane po drugiej strony granicy, to znaczy, że dzieje się coś niedobrego. Najprawdopodobniej struktury danych, model biznesowy i prezentacja/api zostały rozdzielone jedynie na poziomie technicznym, a nie koncepcyjnym. To jak zapisywane są dane, jakie zachowania i reguły są obecne w biznesie oraz w jaki sposób klient komunikuje się z systemem to trzy różne kwestie. Jeżeli każda z nich operuje na identycznych strukturach to są tylko dwie możliwości. Albo architektura warstwowa nie jest tu do niczego potrzebna, bo system (lub jego fragment) to tylko przeglądarka do bazy danych. Albo podział na warstwy został wykorzystany jako czysto techniczne narzędzie, a istotne koncepcje biznesowe zostały gdzieś po drodze zagubione.
Tradycyjna architektura warstwowa
Tradycyjnie najczęściej wydzielane były trzy warstwy: prezentacja / api, logika biznesowa i infrastruktura. Prezentacja zależała od logiki biznesowej, a ta z kolei zależała od infrastruktury, czyli m.in. bazy danych. Czy takie podejście realizuje opisane wyżej cele? Raczej nie.
Wszystko uzależnione jest od infrastruktury, która nie powinna chyba być czymś najważniejszych w oprogramowaniu biznesowym. Jeżeli logika biznesowa zależy od bazy danych, to niemal nieuniknione jest tworzenie modelu na wzór schematu bazy danych. Nic nie pomogą tu DTOsy i przepakowania na granicach warstw. To tylko strata czasu, zarówno programistów jak i procesora. Co więcej zmiana w jednej warstwie będzie pociągać za sobą analogiczne zmiany we wszystkich pozostałych. Związanie zachodzi tu na poziomie koncepcyjnym, a podziały w kodzie są już jedynie niepotrzebnym narzutem, który stał się dla niektórych przyczyną odrzucenia warstw w ogóle.
Na szczęście nie jest to problem z architekturą warstwową jako taką, tylko ze źle poprowadzonymi zależnościami między warstwami. Na szczęście da się to zrobić lepiej.
Hexagonal / Onion / Clean Architecture
Na wstępie zaznaczę, że Hexagonal, Onion i Clean Architecture a także Porty i Adaptery traktuję tu zamiennie. Opierają się one w wspólnych zasadach, a różnią się niuansami. Na potrzeby tego artykułu możemy z powodzeniem traktować je zamiennie. Jako reprezentanta wybrałem Hexagonal Architecture.
Nie będę tu omawiał szczegółów Hexagonal Architektura, gdyż wykracza to poza granice tego artykułu. W sieci można znaleźć bardzo dużo wartościowych opracować na ten temat. Chciałbym jedynie zaznaczyć, że to podejście architektoniczne to nic innego jak warstwy zrobione dobrze. Warstwy są tu ułożone tak, żeby uniezależnić reguły i koncepcje biznesowe od konkretnych przypadków użycia, a te dwa elementy składające się na całość wiedzy biznesowej uniezależnić od technologii. Widzimy tu więc odseparowanie obszarów o różnej istotności dla biznesu oraz podlegających zmianie z różnych powodów.
Ważne jest również, żeby rozwiać mit, że Hexagonal Architecture to bardzo drogie podejście, nawet w porównaniu do tradycyjnych warstw. To zupełna nieprawda. Nakład jest taki sam jak przy wprowadzaniu dowolnych granic architektonicznych. Tam, gdzie jest on uzasadniony, koszt jego wprowadzenia zwróci się wielokrotnie i do tego w bardzo krótkim czasie. Jeżeli koszt wydaje się duży w porównaniu z korzyściami, to najprawdopodobniej ten styl architektoniczny nie jest w danym miejscu potrzeby, albo został źle zaimplementowany.
Kiedy warto stosować warstwy?
No właśnie to kiedy stosować Hexagonal Architecture? Skoro jest to takie dobre i elastyczne rozwiązanie, to może na wszelki wypadek zawsze? Hexagonal Architecture, tak jak każda techniczna koncepcja to tylko narzędzie i należy o tym pamiętać. Ma ono swoje zastosowanie, ale nie służy do wszystkiego i jak każde narzędzie zastosowane w nieodpowiednim miejscu przyniesie więcej szkody niż pożytku.
Hexagonal Architecture stosuje się dobrze, gdy mamy złożony problem biznesowy. Wtedy separacja od technologii bardzo się przydaje, gdyż można zacząć od tworzenie i testowania modeli bez zastanawiania się nad technologią. Jest to właśnie implementacja w podejściu Domain First. Przydaje się ona również do porządkowania kodu w dużych projektach tak, aby logika domenowa, aplikacyjna i komunikacja z systemami zewnętrznymi nie zostały wymieszane. Hexagonal Architecture łączy się więc świetnie z Domain Driven Design. Jest też niezastąpiona, gdy system wymaga elastyczności, np. gdy różne jego wersje są wdrażane dla różnych klientów, mających inne wymagania poza-funkcjonalne.
Architektura warstwowa nie jest jednak dobrym rozwiązaniem do prostych problemów typu CRUD. Jest to wtedy zbędny narzut. Całość implementacji można wtedy zawrzeć w jednej (sic!) warstwie, dopuszczalne jest ścisłe związanie z frameworkami i innymi wykorzystywanymi technologiami. Należy tylko pamiętać o odpowiednim oddzieleniu takich obszarów od reszty systemu i ograniczeniu ich wielkości. Jeżeli zostanie to osiągnięte to w razie czego można wyrzucić całą implementację danego modułu i napisać ją od nowa. Będzie to znacznie tańsze niż inwestowanie w elastyczność na wszelki wypadek.
Seria – Architektura nie musi być kosztowna
Zapraszam do lektury pozostałych postów z tej serii:
Marcin Markowski
Architekt, trener, zwolennik podejścia Software Craftsmanship i ścisłej współpracy z biznesem. Specjalizuje się w modelowaniu opartym o Domain Driven Design i projektowaniu architektury systemów.
Zaczynał od consultingu biznesowego, później przeszedł do IT. Pracował zarówno nad systemami „enterprise”, jak i tworzył od podstaw rozwiązania dla małych firm. Próbował wejść w świat startupów z własnym produktem. Ostatecznie został jednak w IT, gdzie działa jako konsultant i trener.