Wykorzystane grafiki: Heroes of Might and Magic III, do której prawa ma firma Ubisoft Entertainment SA.
⚔️ Heroes of Domain-Driven Design
Ten wpis jest częścią serii, w której opowiadam Ci o stoczonych bitwach w realnych projektach z wykorzystaniem metodyki Domain-Driven Design. Tłumaczę wykorzystane podejścia poprzez analogie występujące w świecie Heroes III. Najwięcej skorzystasz, czytając najpierw poprzednie posty:
- Modelowanie, modularyzacja i produktyzacja + Bounded Context;
- EventStorming, Event Modeling, stawianie granic i wysoka jakość bez code review.
🗺️ Mapa Kontekstów
Po przeczytaniu poprzednich wpisów na pewno już wiesz, iż podział modułów nie jest tylko kwestią techniczną, ale jest ściśle powiązany z zarządzaniem projektem, terminami i organizacją pracy. Omawiana implementacja autonomicznego modułu rekrutacji jednostek będzie niezależna od innych toczących się prac programistycznych. Umożliwia to równoległe prowadzenie prac w wielu modułach i znaczne przyśpieszenie implementacji.
💰 Biznes > Technologia
Na najwyższym poziomie moduły dzielimy biznesowo (np. Creatures Recruitment / Town Building czy w ecommerce: Ordering / Shipping / Pricing), a nie warstwowo (np. controllers / services / repositories, jak w tutorialach frameworków), bo to właśnie biznes zmienia się częściej niż np. silnik bazy danych czy sposób wystawiania REST API, a te podziały służą właśnie do wprowadzania zmian bez konieczności orania całej reszty modułów.
Wyobraź sobie, iż np. przepisujesz właśnie sposób definiowania endpointów, wtedy podział techniczny jest OK, bo zmienisz tylko warstwę Controllers.
Jednak przy takim podziale dodając nową funkcjonalność, już zawsze przelatujesz przez wszystkie warstwy i nie zamkniesz zmian w autonomicznym module, który możesz przetestować w izolacji — bez obaw, iż zmiana popsuje coś zupełnie innego.
Jeśli projekt podzielisz technicznie — optymalizujesz pod zmiany techniczne, a jeżeli biznesowo — pod biznesowe. Proste. Co jest ważniejsze w Twoim projekcie? Co zdarza się częściej? Pod co chcesz optymalizować? Odpowiedzcie sobie na to razem w zespole :)
🧩 API modułu = Command + Query + Event
Aby usiąść do kodu, trzeba ustalić jedynie API modułu, czyli w tym przypadku określić 3 typy możliwych komunikatów wysyłanych do danego modułu. Te same building blocki znajdziesz podczas EventStormingu czy Event Modelingu:
- komendy — tryb rozkazujący | jeden odbiorca (moduł realizujący) | zadania, jakie będą mu zlecane przez inne moduły lub użytkownika;
- eventy — tryb oznajmujący | wielu odbiorców (moduły nasłuchujące, reagujące) | definiuje, o czym będzie informował inne moduły, nie zastanawiając się, czy ktoś tego słucha/reaguje;
- zapytania (ang. queries) - tryb pytający | jeden odbiorca (moduł odpowiadający) | nie zmienia stanu | pokazuje jakie dane będzie pozwalał wyciągnąć dla użytkownika (np. ile mam aktualnie dostępnych jednostek do zrekrutowania?).
Np. moduł świadomy odwiedzającego miasto bohatera i posiadanych przez niego artefaktów będzie mógł zlecać zwiększenie lub zmniejszenie liczby dostępnych jednostek, bez znajomości szczegółów całego procesu rekrutacji.
Taki podział możemy wyrazić, np. stosując technikę Mapy Kontekstów (ang. Context Mapping). Wejdź w ten link i zapoznaj się z różnymi typami relacji między kontekstami. A w tym wpisie pokażę Ci trochę praktycznych przykładów i zasad Mapowania Kontekstów.
Ja zwykle rozszerzam notację Context Mappingu, nanosząc właśnie rodzaje obsługiwanych wiadomości. Daje nam to lepszy ogląd powiązań i odpowiedzialności poszczególnych modułów niż same prostokąty z nazwami połączone strzałeczką.
🔑 Kontekst jest kluczem
Dzięki Mapowaniu Kontekstów zobrazujemy też zależności między modułami i zespołami — kto będzie musiał się do kogo dostosować, a gdzie potrzebne są wspólne ustalenia i partnerstwo.
Nazwa pochodzi od słowa Bounded Context (po polsku tłumaczy się to jako Kontekst Ograniczony), jest to jedna z heurystyk wyznaczania modułów, promowana w tzw. Blue Book — książce Erica Evansa, gdzie skupiamy się na analizie lignwistycznej, w której dane pojęcie ma określone znaczenie, jedynie w ograniczonym zakresie.
Sprzedawce raczej nie interesuje gdzie dany produkt leży w magazynie. Wspólne dla takich obiektów jest tylko ID.
Czyli np. używamy słowa jednostka z kontekście Bitwy i w kontekście Rekrutacji, ale w obu możemy na niej wykonać zupełnie inne operacje (zachowania są tutaj kluczem, nie skupiaj się za bardzo np. na atrybutach klasy — one wynikną z analizy zachowań), co analizowaliśmy też we wpisie Modelowanie, modularyzacja i produktyzacja + Bounded Context. Podobnie jest z Produktem w domenie ecommerce. Zupełnie inne rzeczy możesz z nim zrobić w kontekście Pricingu, a inne, kiedy rozpatrujemy kontekst magazynu, interesują nas też inne atrybuty tego obiektu — wspierające zachowania.
Przeanalizuj teraz poniższą Mapę Kontekstów dla domeny Heroes III, jeżeli masz do niej jakieś pytania, albo coś wg. Ciebie coś powinno być inaczej — to sekcja komentarzy pod wpisem jest odpowiednim miejscem. Nie musisz się nigdzie rejestrować- wystarczy, iż masz konto na GitHubie :)
Możliwy podział na moduły i relacje między nimi. Rozbudujemy i zweryfikujemy wraz z postępem planowania i analizy.
🌳 Pojedyńcze źródło prawdy
Nanosząc na nasz model pytania, możemy upewnić się, iż stosujemy zasadę pojedyńczego źródła prawdy (Single Source of Truth) w naszym systemie. Gdzie znajdziemy odpowiedź na pytanie: Ile jednostek X mam w armii Y? Oczywiście w module Armies. Ten moduł będzie też strzegł zasady, iż nie mogę dodać więcej niż 7 oddziałów jednostek w jednej armii — taka jest reguła domenowa.
⛓️ Spójność natychmiastowa
Co, jednak gdybyśmy nie wydzielili takiego jednego źródła i okazałoby się, iż 2 moduły odpowiadają na to samo pytanie, albo mają tylko część informacji potrzebnych do odpowiedzi? Musielibyśmy się np. odpytać moduł Creatures Recruitment: ile jednostek zrekturowaliśmy a moduł Adventure Map ile jednostek do nas dołączyło i dodatkowo moduł Battling ile straciliśmy w bitwach? Wyobraź sobie tę liczbę requestów (w przypadku mikroserwisów) do wykonania. Dodatkowo nie byłoby możliwości zapewnienia spójności natychmiastowej powyżej omawianej reguły. Nasz system zyskuje skomplikowanie niczym Polska biurokracja, gdzie trzeba chodzić od okienka do okienka, a i tak nikt Ci nie udzieli pewnych informacji:
Istotne jest, iż jeżeli reguły odpowiedzi na jakieś pytania często się zmieniają (np. dochodzą nowe sposoby na zmianę dostępnych jednostek do rekrutacji) to tym bardziej staraj się umieścić wiedzę do odpowiedzenia na pytanie w jednym module. W naszym przypadku to Cretures Recruitment. Dzięki temu nie będziesz musiał angażować ownerów wielu części systemu w taką zmianę i dodawanie ifów w 100 miejscach, a zrobi to jeden zespół.
🏍️ Motor rozwoju
Taka mapa kontekstów może być dla nas niczym klocki LEGO. Pokazuje nasze możliwości biznesowe (ang. capabilities), o których mówiliśmy w pierwszym wpisie serii. Na podstawie mapy widzisz np. iż nic nie zależy od modułu Battling, wiec możemy dowolnie go wymieniać i wprowadzić alternatywny system bitwy — np. za dodatkową opłatą. To potężne narzędzie pracy dla interestariuszy biznesowych i architektów oprogramowania, na którym powinniśmy być w stanie przeprowadzać eksperymenty i symulacje zmian, jakich by wymagały nowe produkty.
❌ UI-Driven Development
Pamiętaj, iż to nie interfejs użytkownika wyznacza podział Twoich kontekstów. To, iż widzę na jednym ekranie statystyki jednostki, ich koszt, a także ile mam ich jeszcze do zrekrutowania, nie znaczy, iż będzie to jeden moduł. Tak samo, jak w systemie ecommerce — dostępność w magazynie pochodzi prawdopodobnie z innego modułu niż cena. Programista i eksperci domenowi, którzy znają wszystkie meandry działania magazynu, mogą wiedzieć kilka o wyznaczaniu cen i promocjach. I to jest jak najbardziej OK, zmniejszamy obciążenie kognitywne i mnogość informacji do przyswojenia koniecznych do szybkiego wprowadzania zmian. O tym jak frontend wpływa na możliwości Twojej architektury, będzie jeszcze w kolejnych wpisach serii.
💊 Lepiej zapobiegać niż leczyć
Możesz wziąć niebieską pigułkę i dalej żyć w błogiej nieświadomości, aż brutalne realia projektu nie wyrwą Cię z tego marazmu.
Context Mapping jest w stanie uwidocznić Ci problemy niedostrzegalne na pierwszy rzut oka, ale niestety odczuwalne przez wszystkich. Nie raz spotkałem się z “udokumentowaniem” podziału na mikroserwisy (gdzie każdy mikroserwis zakładał bycie “niezależnym”) poprzez ich listę i opis odpowiedzialności.
Jednak z takiego opisu nie zauważysz powiązań, które powodują, iż “mała” zmiana jest pracą na cały rok albo dlaczego ten jeden zespół ciągle blokuje Twoją robotę. Na liście nie zobaczysz też relacji i przepływu procesów biznesowych (to jeszcze lepiej uwidocznia się na EventModelingu).
Po co jednak obrazować takie relacje? Żeby móc naprawić dysfunkcje w Twoim projekcie — albo lepiej — w ogóle do nich nie dopuścić (jeśli wykonasz porządną pracę przy planowaniu, o czym możesz poczytać też w poprzednich wpisach serii). Ściśle się to łączy z organizacją zespołów i przenika ze wzorcami z Team Topologies — bardzo Cię zachęcam do przeczytania tej książki, szczególnie jeżeli masz wpływ na kształtowanie zespołów w Twoim biznesie.
🦕 Kontekst Hydry — kąsa wszystkich wokół
Jedną z takich niebezpiecznych relacji może być Customer-Supplier, wydawałoby się najbardziej pożądana. Co tutaj może być trudnego? Jeden zespół dostarcza capability/moduł/serwis, a drugi tego używa? Tak to wygląda w tutorialach… ale nie w praktyce. Słowo “jeden” jest tutaj kluczowe. Rozpatrzmy przykład, gdzie zespół jest jako upstream w tym typie relacji i nie zależy od nikogo. Wydawałoby się, iż to sytuacja idealna, przecież problem się pojawia kiedy ma się dużo zależności. Jednak żadne ekstremum nie jest dobre… Wyobraź sobie, iż ten sam zespół jest w relacji do 4 innych zespołów, które dą dla niego downstream, czyli dostarcza dla nich usługi.
Kontekst Hydry to określenie, jakie używam dla tego specjalnego (ale częstego) przypadku, ponieważ jak Hydra w Heroes III atakuje wszystkich w pobliżu, tak też ten kontekst będzie “kąsał” każdego kto jest z nim w relacji.
Coś takiego powinno być dla Ciebie jasnym sygnałem, iż w obecną architekturą jest coś nie tak. Być może z podziałem zespołów czy organizacji w firmie, na którą jak dotąd nie miałeś wpływu, ale odczuwasz jego skutek wg. Prawa Conwaya. Być może jednak z podziałem technicznym, którego dokonaliście w gronie “ludzi z IT”.
Taka zależność jak zobrazowana poniżej nie jest bezpośrednia, dlatego, jeżeli jesteś jednym z zespołów downstream, możesz tego nie widzieć. Dopiero taka mapa, która powinna być ważnym artefaktem dla wszystkich zespołu, niczym Graal w Heroes III może uwidocznić tę patologiczną sytuację. Nie życzę Ci, żebyś kiedykolwiek był w zespole “Hydry” w takim przypadku, bo stres związany z byciem otoczonym na pewno będzie Ci towarzyszył, kiedy gonią deadliny.
Możliwy podział na moduły i relacje między nimi. Rozbudujemy i zweryfikujemy wraz z postępem planowania i analizy.
Istnienie takiego kontekstu odczują wszyscy w organizacji, choćby jeżeli nie rozrysujesz mapy — to ta relacja przecież tam istnieje. Łatwo ją wytworzyć realizując naiwne rozwiązania serwisów skupionych na encjach (co widzisz na powyższym diagramie, w przeciwieństwie do tego z początku wpisu). I bardzo ciężko się jej pozbyć niczym zapachu po sosie czosnkowym z buzi.
Jakie będą jeszcze objawy takiego powiązania, nie wspominając już o “zapachach kodu”?
- Taką silną zależność odczuję też managerowie, bo zespoły będą musiały razem priorytetyzować swoje taski. Mogą zacząć się bitewki polityczne, bo przecież dla managera to zawsze jego projekt jest najważniejszy i on będzie raportował opóźnienia. Zespół upstream nigdy nie znajdzie czasu spełnić próśb wszystkich zespołów — niektóre może będą choćby ze sobą sprzeczne! jeżeli już widzisz taką sytuację, to wiedz właśnie, iż “coś się dzieje” i poświęć czas na sporządzenie takiej mapy, aby dostrzec bolące miejsce.
- Może się tak zdarzyć, iż trzeba choćby synchronizować wdrożenia, bo w ogóle nie osiągnęliśmy “niezależności” promowanej przez mikroserwisy. Kaskada zmian jest tak kolosalna, iż nie sposób zrobić tego stopniowo.
🗿 Nie model danych, a procesy
Znasz ten problem? To jest często oznaką nieodkrycia odpowiednich poddomen i połączenia ich w jeden moduł. Być może u Ciebie jest mikroserwis Users albo Products z wielkim gąszczem ifów i statusów? To może wskazywać też na taką Hydrę, której czym prędzej trzeba poodcinać łby poprzez dostosowanie architektury do procesów biznesowych, Taka sytuacja jak na diagramie z Hydrą, kiedy podzieliliśmy nasz system, bazując na rzeczownikach — wg. encji, prawie jak model bazy danych, a nie utworzyliśmy modelu dziedzinowego opartego na procesach biznesowych.
Uwaga! I tutaj wydaje się, iż został zastosowany podział “biznesowy”, a w rzeczywistości jest on stricte techniczny — obrazuje nasz model danych (wyobrażenie developera), a nie to jak działa dana organizacja.
Taki Kontekst Hydry to będzie kod najczęściej zmieniany i najbardziej znienawidzony przez programistów. W kodzie to wszyscy zależą od tego kontekstu, ale realnie — jako wzorzec organizacyjny, to on zależy od wszystkich, bo musi spełniać zachcianki innych zespołów. A odpowiedź “mam to w backlogu” będzie często stosowanym wytrychem, aby zyskać trochę czasu, mijając kogoś z zespołu downstream na biurowych korytarzach.
🧠 Włącz myślenie
Pamiętaj rozszerzać mapę o to, co dla Ciebie jest pomocne, choćby jeżeli to nie jest “by the book”. Przywołam tutaj bardzo inspirujący cytat, który widzę w wielu relacjach u Macieja Aniserowicza, na monitorze jego komputera: “Question Authority, Think Yourself”.
W moim przypadku komendy i eventy pozwalają dostrzec ewentualne wyciekanie języka danego modelu do innych kontekstów albo zbyt silną zależności. Także, jeżeli dwa konteksty odpowiadają na to samo pytanie… Wtedy kończy się szybkie wdrażanie, bo zespoły nie są w stanie dowozić wartości biznesowej niezależnie i ciężko wskazać, kto jest odpowiedzialny za daną część systemu, bo wszystko płącze się niczym spaghetti.
👥 Po pierwsze ludzie
Jako architekci/modelarze/developerzy nigdy nie jesteśmy ludźmi oderwanymi od biznesu. W dzisiejszych czasach choćby niektóre biznesy nigdy by nie zaistniały, gdyby nie software. Nie po to zostałem programistą, żeby z ludźmi pracować… - ten cytat niestety nigdy nie był prawdziwy. Architektura systemu to ciągłe zarządzanie relacjami i zespołami. Powodzenia!
Jeśli chcesz wiedzieć, jak sprawnie zaatakować taką hydrę i rozwiązać tę ciężką sytuację, to zapraszam Ciebię na konsultacje TUTAJ. Możesz się też ze mną najpierw skontaktować, pisząc na maila: mateusz@nakodach.pl