Marcin Markowski - 14.03.2019

Od czego zacząć projektowanie architektury?

Jak podejść do projektowania architektury systemu? Co trzeba określić już na start, a co może poczekać? Od czego mogą zależeć wybory architektoniczne i kiedy jest najlepszy moment na ich podjęcie? Pytania te nie mają niestety jednej prostej odpowiedzi. Przyjęte podejście ma jednak niezwykle istotny wpływ na powstający system.

Poznawanie biznesu

Umysł techniczny lubi (i niestety jest w stanie) sprowadzać wszystko do problemów technicznych. Dlatego tak często projekty zaczynają się od wyboru technologii oraz pisania własnych „niezbędnych” frameworków. Nie jest to najlepsza droga.

Gdy przychodzimy do pracy z systemem, który jest już od jakiegoś czasu na produkcji, zwykle narzekamy na skutki takiego podejścia. Niezadowolenie z zagmatwania kodu i rozwiązań technicznych, wywołuje chęć zrobienia tego lepiej jak tylko będzie okazja. Okazja przychodzi wraz z nowym projektem lub modułem. Realizujemy wtedy nasze marzenie i kończy się to … jak zwykle. Przyczyną tych ciągłych porażek jest niewystarczający zasób wiedzy potrzebnej do podjęcia trafnych decyzji technologicznych.

Przez cały cykle życia produktu, ale szczególnie na początku, istotne jest poznanie biznesu i zrozumienie, co jest tak naprawdę do zrobienia. Decyzje technologiczne nie powinny mieć pierwszeństwa. Budowy systemu lepiej nie zaczynać od określenia warstw, podziałów na (mikro)serwisy, czy mechanizmów integracji. Nie da się tego zrobić nie rozumiejąc procesów i reguł biznesowych.

Z kolei biznesu nie da się dobrze poznać bez eksperymentów z modelem, które najlepiej jak najszybciej przełożyć na prototyp kodu. Dlatego w pierwszym rzędzie nasza architektura powinna wspierać tworzenie modelu domenowego, eksperymentowanie z nim i testowanie go. Na pozostałe kwestie przyjdzie czas później.

Tu z pomocą przychodzi nam Domain Driven Design, którego istotą jest proces uczenia się biznesu w bardzo szerokim kontekście. Strategiczne wzorce DDD pozwolą nam poznać granic istniejących w biznesie i określić wagę poszczególnych obszarów. Dzięki temu będziemy w stanie skupić się na tym co najistotniejsze i dobrać właściwe środki dla każdej części systemu.

Oddzielanie głębokiego modelu od CRUDa

Poznanie biznesu pozwoli nam oddzielić te fragmenty, które faktycznie wiążą się ze złożonymi regułami, od tego co jest zwykłym CRUDem. Dzięki temu głęboka analiza i bardziej wyrafinowane środki modelarskie (Building Blocki DDD, Hexagonal Architecture, CQRS, etc.) będą stosowane tylko tam gdzie jest to uzasadnione.

W każdym systemie jest wiele obszarów, które sprowadzają się do zbierania i przeglądania danych. W takich przypadkach można z powodzeniem zastosować jedną warstwę, która będzie zawierać zarówno API, walidację jak i komunikację z bazą danych. Dzięki temu nasz głęboki model zostanie odciążony z elementów, które nie są w nim konieczne. Jest to bardzo istotne, gdyż nawet dobrze zaprojektowany model dziedzinowy może całkowicie stracić swą czytelność, gdy zostanie zaśmiecony przez mnóstwo CRUDowych operacji.

Modularyzacja

Poznanie biznesu pozwoli nam również na podzielenie systemu na mniejsze części we właściwy sposób. Nie ma nic gorszego niż modularność wprowadzona na bazie czysto technicznych kryteriów. Model nie jest już wtedy odwzorowaniem rzeczywistości. Jak więc wprowadzić zmiany w granicach między poszczególnymi częściami, gdy biznes lub wiedza na jego temat ulegnie zmianie? Nie jest to już proste, gdyż w kodzie nie można odnaleźć aktualnych podziałów biznesowych. Nasza nowa wiedza nie pasuje więc do systemu i nie ma dla niej naturalnego miejsca. Jak kończą się takie sytuacje? Nowa wiedza wprowadzana jest w sposób pozwalający jak najszybciej spełnić testy akceptacyjne. Nie trzeba chyba dodawać jak szybko projekt prowadzony w ten sposób zamienia się w słynne spaghetti.

Tu znowu z pomocą przychodzi nam DDD z koncepcją Bounded Contextów i modułów. Dzięki temu nasz system nie będzie jedną wielką całością, w której wszystko wiąże się ze wszystkim. Nie będzie on również zlepkiem części niemających znaczenia biznesowego.

Wydzielanie kontekstów i modułów zgodnie z podziałami istniejącymi w biznesie wcale nie jest jednak łatwe. I to nie ze względów technicznych, tylko z braku wiedzy biznesowej. Czasami nie ma jej jeszcze w zespole i potrzebnych jest więcej rozmów z ludźmi z biznesu, czasami sam biznes dopiero się kształtuje, lub właśnie przechodzi jakieś istotne zmiany. Im miej wiedzy tym ostrożniej należy podchodzić do przeprowadzania granic. Lepiej jest początkowo stawiać ich mniej i wprowadzać kolejne dopiero, gdy wiedza biznesowa to uzasadnia. Znacznie łatwiej jest podzielić za duży kontekst, niż z trzech za małych zszyć dwa nieco większe.

Modularność daje nam też szansę efektywnego refactorowania naszego kodu. Jeżeli jakiś właściwie odseparowany moduł wymaga istotnych zmian, a jest odpowiednio mały, to da się go w całości przepisać w tydzień lub dwa. Jest to rozsądny koszt i nie ma ryzyka modyfikowania nieprzeniknionej gęstwiny zależności. Jest to szczególnie ważne w miejscach, w których zastosowaliśmy prostą, a w konsekwencji mało elastyczną architekturę. Jeżeli CRUDowy moduł okazał się czymś znacznie więcej niż się pierwotnie spodziewaliśmy, to możemy go napisać zupełnie od zera stosując Hexagonal Architecture i CQRS, lub inne bardziej wyrafinowane podejście.

Odkładanie decyzji technologicznych

Na starcie projektu nasza wiedza o wymaganiach zarówno funkcjonalnych jak i poza-funkcjonalnych zwykle nie jest zbyt duża. Dlatego tak ważne jest odroczenie decyzji, których nie trzeba podejmować. Równie ważne jest to, żeby system był elastyczny tam, gdzie brakowało informacji do wyboru optymalnego rozwiązania.

Z drugiej strony, odkładanie decyzji technologicznych nie może trwać zbyt długo. Tu wchodzimy w bardzo trudny do określenia temat, co to jest wystarczający up front design. Jeżeli jest, go za dużo to mamy do czynienia z sytuacją znaną z tradycyjnie realizowanego waterfalla. Architektura tworzona w ten sposób ma minimalną szansę sprawdzić się w rzeczywistości, gdyż została zaprojektowana na podstawie zbyt małej wiedzy. Jeżeli jest go za mało, to popadamy w źle pojęty agile, w którym liczymy na to, że dobra architektura sama się wyłoni. To jaka jest właściwa proporcja zależy od konkretnego przypadku i nie da się podać złotej reguły. Wszystko zależy od naszej wiedzy o funkcjonalnych i poza-funkcjonalnych wymaganiach.

Rozwiązania techniczne można wprowadzać stopniowo, a zaczynać najlepiej od tych najprostszych. Często w wielu miejscach systemu będą one wystarczające przez cały cykl życia naszego produktu. Dobra architektura zapewnia możliwość zmiany rozwiązań technicznych wraz z rozwojem potrzeb.

Seria – Architektura nie musi być kosztowna

Zapraszam do lektury pozostałych postów z tej serii:

  1. Wstęp
  2. Co to jest dobra architektura ?
  3. Od czego zacząć projektowanie dobrej architektury ?
  4. Architektura wspierająca podejście Domain First
  5. Wnioski

Marcin Markowski

Zdjęcie Marcin Markowski
Trener

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.