⚔️ Moc i magia Domain-Driven Design w świecie Heroes III: Modelowanie, modularyzacja i produktyzacja + Bounded Context

3 miesięcy temu

Grafika została złożona ze zrzutów ekranu, zrobionych podczas rozgrywki w grę Heroes of Might and Magic III, do której prawa ma firma Ubisoft Entertainment SA.

🧠💪 Po przeczytaniu tego wpisu:

  • dowiesz się jak modularyzacja wpływa na możliwości rozwoju biznesu i stwarza szanse na nowe produkty;
  • przełożysz karteczki z EventStormingu i Event Modelingu 1 do 1 na działający kod;
  • poznasz sposób na codzienny i szybki rozwój Twoich umiejętności modelowania procesów biznesowych;
  • zadbasz o wysoką jakość projektu lepszym sposobem niż code review;
  • modelując procesy weźmiesz pod uwagę różne perspektywy zaangażowanych osób, takie jak: ux/ui, frontend, backend, analityka;
  • z odpowiedniej notacji modelu wygenerujesz testy jednostkowe dzięki ChatGPT;
  • unikniesz komplikowania kodu bardziej niż modelowany proces biznesowy;
  • zastosujesz wzorzec Decider do wyrażania logiki biznesowej w funkcyjnym stylu;
  • poznasz różne praktyki modelarskie, które będą inspiracją do dalszego rozwoju;
  • twoje programowanie już nigdy nie będzie takie samo.

Mógłbym to wszystko opisać na przykładzie kina czy koszyka zakupowego? Na pewno! Jednak po co wciąż wałkować ten sam temat…? Czas wejść w świat bohaterów, magi, elfów i innych fantastycznych stworzeń.

Poniżej widzisz kawałek Event Modelingu autonomicznego modelu rekrutacji jednostek w Heroes III. Ale jak doszliśmy do tego momentu!? I co w tym przypadku oznacza “model” i to jeszcze “autonomiczny”!? Przecież nikt nie rekrutuje Aniołów na samym początku gry… Tak samo nikt nie wie od razu, jak wygląda dany proces biznesowy i jakie abstrakcje użyć do jego wyrażenia.

Strudzony skomplikowanym programowaniem Herosie! Czas wyjść z tawerny i zagłębić się w świat mocy (sprawdzonych wzorców i heurystyk) oraz magi (zwanej też intuicją) modelowania w myśl Domain-Driven Design. 🧙‍♂️

Reprezentacja procesu rekrutacji jednostki. Event Modeling pozwala nam opowiedzieć historię jak film i połączyć różne warstwy systemu: akcje użytkownika, projekty interfejsów, REST API, zapis danych itp. na jednym diagramie. Dzięki temu wiemy, iż nie ma luk w wymaganiach. Zobrazowane zależności pokazują, w jakich miejscach praca może toczyć się równolegle. Wynikiem jest projekt, który przekładamy 1 do 1 na kod i nie tracimy czasu w dyskusje podczas code review.
(Kliknij obrazek, aby powiększyć)

💾 Z życia na kodach

Mój pan od fizyki nie raz mawiał, iż po rozwiązaniu setek zadań z rysowaniem wektorów, teraz wszędzie widzi działające na świat siły. Spotykając różnorodne rozwiązania IT na każdym kroku, mój mózg działa podobnie. Nawet kiedy stoję w kolejce do fast-fooda, to w mojej głowie już rozrysowują się potencjalne procesy i interakcje między modułami systemów obsługujących zamawianie jedzenia i organizację kuchni.

Odpowiednie moduły pomogą Ci odkryć szanse na nowy produkt i potencjalne miliony dla przedsiębiorstwa. W XXI wieku software jest nierozerwalnie związany z biznesem i może być motorem jego rozwoju (lub zupełnie przeciwnie — ostatnim gwoździem do trumny).

Niedawno po kilku latach przerwy odpaliłem Heroes III i do razu zacząłem zastanawiać się, w jaki sposób zaprogramowałbym coś podobnego na bazie moich doświadczeń z komercyjnych projektów (nie programuję gier, ale automatyzuję procesy biznesowe, takie jak: publikacją ogłoszeń o pracę, praca w call center i w banku itp.).

Wróciły też flashbacki z moich pierwszych komercyjnych projektów i błędów, jakie popełniałem, podchodząc do programowania, bez adekwatnego procesu zdobywania wiedzy (ang. knowledge crunching) i planowania. Choć to tylko (w tym przypadku aż!) gra, to można w niej znaleźć wiele analogii do realnych procesów biznesowych, które będę odkrywał razem z Tobą (pierwsze już w tym wpisie) i przekładał na kod.

👁 Klątwa wiedzy

Statystyki bohatera wpływają na jednostki w bitwie, a wynik bitwy przecież na armię bohatera. Bohatera możemy zatrudnić w tawernie, która może być zbudowana w mieście. Armię za to rekrutujemy w siedliskach jednostek, które mogą, ale nie muszą być w mieście. Tawerna zresztą tak samo. Dostępność jednostek odnawia się, co tydzień. No, chyba iż astrologowie ogłaszają tydzień plagi. Jeszcze oczywiście musimy mieć na ich zakup zasoby, które są zbierane na mapie… Aaaa… i pamiętaj też rozwijać bohatera! - to tylko kawałek podanych wymagań, gdyby spytać “eksperta” o procesy, jakie zachodzą w świecie Heroes III.

Wyobraźcie sobie wyraz twarzy mojej żony, kiedy chciałem, aby zagrała ze mną i pierwszy raz tłumaczyłem jej zasady gry jako taki “ekspert”. Wymiękła po chwili… Nie inaczej jest, kiedy tzw. biznes próbuje nam przekazać swoje wymagania co do projektu, jeżeli nie będziemy mieli głowy na karku. Dlatego naszym zadaniem (i odpowiedzialnym obowiązkiem) jest przeprowadzenie procesu knowledge crunching, czyli zdobycia wiedzy domenowej od osób, które ją mają. Na szczęście istnieją do tego sprawdzone metody, opracowane przez community Domain-Driven Design. Zastosuję niektóre z nich, a więcej znajdziesz na GitHubie DDD Crew. Ten wpis jest pierwszym z serii. W kolejnych będziemy wchodzić na wyższy poziom Gildii Magów i bardziej zagłębiać się w przywołane techniki.

🗿 Czym jest model?

Model to nie kopia realnego świata, ale jego reprezentacja zrobiona w danym celu: rozwiązania konkretnego problemu i odpowiedzenia na specyficzne pytania. Jako programiści ciągle tworzymy tzw. abstrakcje, aby móc zrozumieć i zaimplementować wymagania projektowe. Jednak z różnych modeli (czy abstrakcji) korzystasz codziennie, może choćby nie zdając sobie z tego sprawy. Gdy jedziesz samochodem, to linie tworzące drogę na nawigacji nie są przecież tą ulicą, po której rzeczywiście jedziesz, ale odpowiadają Ci na pytanie: “jak najszybciej dojechać do celu”.

🪑🪚 Meble i punkt siedzenia

W takim razie, jaki to “dobry model”? Zależy, kto pyta. Zamawiając kuchnię na wymiar, otrzymałem dwa modele: jeden rysunek techniczny (po lewej) i drugi w 3D (po prawej). Jeśli chcę zobaczyć, jak zgrywają się kolory i wyobrazić sobie czy układ urządzeń będzie optymalny — to model 3D jest właściwy. Jednak dla osoby, która ma wycinać i składać meble będzie on bezużyteczny, bo nie ma na nim podanych wymiarów. To przykład na 2 różne reprezentacje tej samej rzeczywistości — obie użyteczne, ale w różnych kontekstach.

Model jest adekwatny tylko w danym kontekście.

Czy da się wszystkie konteksty zobrazować przy pomocy jednego modelu? Tak często pokazują Ci tutoriale czy dokumentacja dla danego frameworka (niemożliwe jest streścić wszystkie książki o dobrych praktykach w takim miejscu). Niestety choćby developerzy z wieloletnim doświadczeniem wciąż próbują to powtórzyć w aplikacjach z rozbudowanymi procesami biznesowymi. I to się sprawdza, do momentu, kiedy zaczynasz w końcu gubić się w tym gąszczu ifów. Tłumaczą się choćby wtedy na opak rozumianym “pragmatyzmem” i mówią: “potem to poprawimy” - a przecież wiesz, iż to “potem” nigdy nie następuje. Dlatego już teraz zainwestuj czas, aby poszerzyć zakres znanych Ci narzędzi, aby dobierać adekwatne rozwiązania do danej klasy problemu. I koniecznie zastosuj je w praktyce — dopiero wtedy pokazują pełnię swoich możliwości!

🗄️ Szufladkowanie modeli

Pokażę Ci sposoby, dzięki którym będziesz mógł zapanować nad swoim kodem i ten efekt zagubienia nigdy nie nastąpi, albo zostanie ograniczony w granicach modułu. To tak jak z Twoją komodą w sypialni. Najważniejsze wiedzieć, co znajdziesz w jakiej szufladzie, a potem zastosować strategię odpowiednią do sytuacji: spodnie ładnie poskładasz, a skarpetki prawdopodobnie rzucisz luzem. Każdy, kto jednak próbował wykonać jeden wielki model, składający się z setek tabel (albo komodę z jedną wielką szufladą), nadający się do “wszystkiego” - prędzej czy później poległ (albo polegli programiści, którzy przyszli utrzymywać ten soft później). Skupmy się więc na podziale na logiczne podproblemy biznesowe, a nie na techniczne warstwy czy mikroserwisy. Dzięki temu każdy z tych problemów będzie można rozwiązać w prostszy (zapewne też tańszy) sposób i ustrzeć developerów od efektu przeładowania kognitywnego (ang. cognitive overload).

Ale wiesz, gdzie szukać.
Zrodlo: https://forum.pytamy.online/t/u-kogo-jest-taka-szuflada/2984

Model nie może istnieć bez żadnych granic, bo wtedy będzie odzwierciedlał świat realny (tak jak szuflady wyznaczają granice w komodzie). Wszystkie modele są niepoprawne, ale niektóre są użyteczne w danym kontekście (jak wspomniane projekty kuchni). Właśnie, aby znaleźć odpowiedni model do sytuacji, musimy spojrzeć na problem do rozwiązania z różnych perspektyw.

🫣 Perspektywa BEING: Czym jestem?

Jednostki w Heroes III mają swoje nazwy oraz określony poziom. Każda z nich należy do innej frakcji, ale są też jednostki neutralne. Niektóre jednostki można ulepszać. Każda jednostka ma określony koszt zrekrutowania, a także bazowy przyrost (ile można zrekrutować w każdym tygodniu). Jednostki mają określone statystyki, szczególnie istotne w czasie bitwy.

📜 Otwierasz planszówkę? To najpierw instrukcja!

Kiedy skupiasz się na rzeczownikach i strukturach danych, a przed oczami już rysują Ci się tabelki bazy danych dla jednostki, to jakie pytania doprecyzowujące na podstawie tego opisu możesz zadać, aby zrobić lepszy “model”? I jakie odpowiedzi prawdopodobnie otrzymasz?

  • Pytanie: Co ma jednostka? Odpowiedź: Nazwę…
  • Pytanie: Ile znaków może mieć nazwa jednostki? Odpowiedź: Różnie… chyba maksymalnie 50, żeby UI nam się nie rozjechał.
  • Pytanie: W jaki sposób określamy poziom? Odpowiedź: Liczbą od 1 do 7.
  • Pytanie: Ile usprawnień może mieć jednostka? Odpowiedź: Od 0 do 1.

Skoro nam tak dobrze poszło z określeniem “co ma jednostka”, to moglibyśmy brnąć w to dalej i rozrysować całość bazy danych, ale zobacz co na to, mówi Król Julian.

Jednym plusem takiego podejścia jest to, iż nie musiałem marnować na nie zbyt wiele czasu. Robotę za mknie zrobił ChatGPT i pięknie rozrysował tabelki. Takich programistów prawdopodobnie gwałtownie zastąpi AI. Jednak Ty przecież chcesz się utrzymać w branży? To czytaj dalej.

Jeden wielki model bazy danych dla całych Heroes III wykonany przez ChatGPT. Tylko po co? Na studiach dostałbym pewnie za to 5, ale w praktyce należy się nie więcej niż 2.

Zaczynać projekt od schematu danych to jak otworzyć grę planszową (swoją drogą, niedawno wyszła wersja Heroes III w tej formie), obejrzeć zawartość: “OK, mam 50 kart, mam też planszę”… i próbować grać, w ogóle nie czytając instrukcji. Czy z rozrysowanych tabelek wiesz już, co musisz zrobić najpierw? Albo gdzie można zrównoleglić pracę developerów? Czy mogą nad tym pracować osobne zespoły? Albo jakich zależności czy projektów interfejsu użytkownika jeszcze brakuje? Aż wreszcie: czy przybliża Cię to do zrozumienia zachodzących procesów biznesowych? Czy wiesz, po co jednostki mają poziomy, albo jaki jest sens w ich ulepszaniu? Najpierw skupmy się na zrozumieniu istoty problemu. Resztą, taką jak szczegóły bazy danych, zajmiemy się później.

😌 Spoko, spoko — przecież ja robię OOP i mam klasy, a nie tabelki!

W takim razie jak “obiektowo” zamodelować jednostkę? Poniżej widzisz wykonanie dzięki Kotlina i obiekt sparsowany do JSONa (z przykładowymi wartościami).

data class Creature( val id: String, val level: Int, val faction: Faction, val growth: Int, val upgrades: Set<Creature>, val cost: Resources, val attack: Int, val defense: Int, val damage: Range, val health: Int, val speed: Int, val shots: Int = 0, val size: Int = 1, val spells: Set<Spell>, val abilities: Set<SpecialAbility>, )
{ "id": "Angel", "level": 7, "faction": "castle", "growth": 1, "upgrades": [ "Archangels" ], "cost": { "gold": 3000, "crystal": 1, "wood": 0, "ore": 0, "sulfur": 0, "mercury": 0, "gems": 0 }, "attack": 20, "defense": 20, "damage": { "low": 30, "high": 50 }, "health": 200, "speed": 12, "shots": 0, "size": 1, "spells": [], "abilities": [ { "type": "HATE", "creatures": [ "Devil", "ArchDevil" ] }, { "type": "ConstRaisesMorale", "amount": 1 } ] }

🔴 Przypadkowa złożoność i wszystkie testy na czerwono

Zmienne są dobrze nazwane? Tak. Czy te wszystkie atrybuty należą do jednostki albo są z nią w relacji? Tak. W takim razie: czy coś w tym złego? Zanim odpowiemy sobie na to pytanie, posłuchajmy jeszcze dwóch dialogów programisty z ekspertami domenowymi:

  • Ekspert #1: “Bohater ZAWSZE należy do jakiegoś gracza.”
  • Programista: “Jesteś tego pewien? Czy kiedykolwiek może się zdarzyć, iż bohater nie będzie należał do gracza?”
  • Ekspert #1: “Nie nie… przecież bohater na mapie zawsze jest pod jakąś flagą. To się NIGDY nie zmieni.”

Zadowolony, wykonujesz idealny model spełniający wymagania biznesowe, we współpracy z ekspertami, czujesz, iż zęby zjadłeś na DDD, a to prawdziwy Clean Code:

data class Hero( val id: HeroId, val player: PlayerId )

Po pewnym czasie rozmawiasz z kolejnym ekspertem:

  • Ekspert #2: “W Tawernie kupujemy bohatera, który nie należy do żadnego gracza.” 🤯
  • Programista: “Jak to? Przecież Ekspert #1 mówił, iż bohater ZAWSZE należy do jakiegoś gracza.”
  • Ekspert #2: “Musiało mu się coś pomylić. Ja tutaj pracuję dłużej i wiem lepiej.”

Co się stanie teraz z Twoim kodem? Wprowadzasz modyfikację, bo przecież kod musi odzwierciedlać działanie biznesu.

data class Hero( val id: HeroId, val player: PlayerId?, val cost: Resources // koszt najęcia bohatera )

To w końcu mała zmiana — tylko dodanie możliwości nulla w polu player. Czy to koniec pracy? W żadnym wypadku! Teraz wszystkie testy, jakie tworzyły instancję Hero, świecą się na czerwono i muszą zostać zmienione (czyli nie chronią Cię przed regresją). Dodatkowo wszędzie gdzie odwołujesz się do hero.player wprowadzasz ifa sprawdzającego nulla.

Jak uderzają takie zmiany w Twój projekt?

Jeszcze pół biedy, jeżeli chociaż stosujesz Kotlin (czy inny język z Null Safety) i poinformuje Cię o tym kompilator, a nie błąd na produkcji. Nawet jeżeli się z tym uporasz, to teraz chcesz zmergować zmiany i… bam (jak to mówi mój 1-roczny syn)! Okazuje się, iż inny programista już je nadpisał i mamy konflikt! A Twoje morale i efektywność pracy spadają niczym w armii bohatera w Heroes III, gdy pomieszamy różne frakcje. Czy nie prościej, zamiast modyfikować, byłoby zastosować Open-Closed Principle na poziomie Twojej architektury i mieć dwa osobne modele? Nad którymi bez problemu mogą pracować choćby oddzielne zespołu programistów? Przykład widzisz poniżej.

adekwatnie podzielenie modelu zwiększa liczbę klas, ale ostatecznie tworzy więcej modeli, o mniejszej złożoności.
(Kliknij obrazek, aby powiększyć)

🐉 Złe nawyki — to tutaj czają się smoki

Inne atrybuty opisujące Bohatera będa potrzebne w kontekście tawerny, a inne podczas ruchu na mapie. Dodaj po prostu kolejny namespace/moduł czy jak to się nazywa w Twoim środowisku i zrób 2 osobne klasy. Nic Cię tutaj nie ogranicza. Chyba iż niestety dalej myślisz tabelką Hero i dodaniem kolumny nullable, albo relacjami między tabelkami. Tak nas uczyli od początku i ze złymi nawykami walczy się najciężej. To właśnie tutaj czają się potężne smoki, ale jeżeli je pokonasz — Ty i Twój projekt zgarniecie większe nagrody niż po pokonaniu Smoczej Utopii.

Smocza Utopia — W Heroes III, jeżeli chcesz zdobyć wartościowe skarby, musisz pokonać strzegące ich smoki.

Kiedy słyszysz od ekspertów sprzeczne informacje na temat tego samego rzeczownika, to próbując pogodzić je w jednym modelu, wprowadzasz tzw. accidental complexity. Teraz każdy programista, który choćby zna prawie cały system (ale nie tawernę i najmowanie bohatera) spyta Cię: “stary, a dlaczego tutaj muszę sprawdzać null”? I to jeszcze najmniej groźna forma tego problemu… tylko oboje stracicie trochę czasu.

W efekcie problem istniejący w kodzie, ale nie w samej domenie — staje się cięższy do zrozumienia, niż jest w rzeczywistości. Czy jakiś z ekspertów Cię okłamuje albo jest niekompetentny? Nic z tych rzeczy! Przecież w tawernie możesz bohatera kupić, ale już nie można kupić żadnego z bohaterów poruszających się po mapie, bo należą do Ciebie lub innego gracza. Takie rozbieżności w zeznaniach “biznesu” to wymowny znak, iż powinniśmy teraz porozmawiać o zachowaniach. Rzeczownik “bohater” ten sam, ale zachowania, operacje możliwe do wykonania i kontekst — zupełnie inne.

🤔 Perspektywa BEHAVING: Co robię?

Posłuchaj teraz kilku wypowiedzi graczy Heroes III:

  1. Nie zdobędę tej kopalni, bo broni jej horda jednostek.
  2. Muszę wybudować ten budynek, bo pozwoli mi ulepszyć jednostkę 7 poziomu.
  3. Na razie nie będę atakował tą jednostką, tylko poczekam.
  4. Koszt rekrutacji tej jednostki jest za duży, nie mam tyle zasobów w moim skarbcu.
  5. O nie! Tydzień plagi, a ja nie wykupiłem całej populacji jednostek.
  6. Nie mogę się ruszyć, bo ta jednostka została oślepiona.

Kiedy modelujesz zachowania, a nie struktury danych, to możesz zadać znacznie więcej pytań, przybliżających Cię do zrozumienia istotnych elementów analizowanego procesu:

  1. Czy zdobycie kopalni zawsze wiąże się z walką?
  2. Co musi stać się jeszcze poza wybudowaniem budynku, abym mógł ulepszyć jednostkę? Czy ulepszać mogę tylko w wybudowanych przeze mnie siedliskach?
  3. Można atakować i poczekać? Jakie są jeszcze możliwości?
  4. Skąd biorą się zasoby w skarbcu? Jak określany jest koszt jednostki?
  5. Tydzień plagi? Co to jest? Jak często się zdarza? Czy są jeszcze jakieś inne “specjalne tygodnie”?
  6. Czy tylko “oślepienie” powoduje brak możliwości ruchu jednostki?

Nie ma niezawodnego sposobu, ani działającego zawsze i w 100% procesu na zdobycie wiedzy domenowej. Tutaj potrzebne jest doświadczenie i intuicja. Dlatego też praktykujmy modelowanie, gdzie tylko się da, choćby grając w gry. Bo to jest istota naszej pracy, klepanie kodziku to już jedynie formalność. Jednak możesz sobie pomóc, stosując wzorce, którzy wymyślili już inni i wykorzystując wiele technik strategicznego DDD, takich jak faza Big Picture EventStormingu.

🦅 EventStorming Big Picture, widok z lotu ptaka

Jak przeprowadzić taki warsztat opisałem dokładnie we wpisie 🍕 Przepis na udany EventStorming krok po kroku!. Teraz spójrz poniżej na zdarzenia, jakie zidentyfikowaliśmy w trakcie takiej sesji w domenie Heroes III. Obecnie skupimy się na wycinku, gdzie eksperci domenowi wspominali nam o jednostkach, czyli w luźnym tłumaczeniu z angielskiego: Creature.

Event Storming pozwala dostrzeć różne konteksty, w jakich działają eksperci domenowi.
(Kliknij obrazek, aby powiększyć)

Zaprezentowane karteczki symbolizują zdarzenia na różnym poziomie abstrakcji i odwołujące się do różnych kontekstów.

  • Niektóre osoby skupiły się na detalach walki, choćby naniosły szczególne efekty czarów, jak np. oślepienie (Creature Blinded).
  • Inne opisały rekrutację (Available Creatures Changed / Creature Recruited) czy możliwe operacje do wykonania w miastach (Creature: Growth Changed / Upgraded / Deposited in Garrison).

Przypomnij sobie jeszcze raz nasz cały “model” jednostki, oparty na rzeczownikach. Czy w każdym przypadku potrzebujemy wszystkich pokazanych atrybutów o jednostce, aby wiedzieć, czy coś może się wydarzyć? Czy możliwe ulepszenie zależy np. od wielkości jednostki w trakcie bitwy? Nie. Czy koszt jednostki zależy od punktów życia (ang. hit points)? Oczywiście, iż nie. W takim razie nie powinniśmy mieszać tych rzeczy w jednym modelu. To brzmi jak oczywistość, ale tak zwykle robione było legacy, które ktoś musi utrzymywać (mam nadzieję, iż nie Ty)…

🏃‍🏃 Absurd goni absurd, a uciekają pieniądze

Analogiczny przykład dawał Udi Dahan w trakcie szkolenia “Learn Advanced Distributed Systems Design”:

  • Czy, aby wiedzieć, iż mogę zarezerwować pokój w hotelu, to muszę znać jego nazwę? Czy potrafisz sobie wyobrazić niezmiennik: “jeśli nazwa pokoju zaczyna się na literę A, to możliwe są rezerwacje jedynie w piątki”?

Brzmi absurdalnie? No to nie łączmy listy rezerwacji w jeden obiekt z nazwą pokoju, choćby poprzez ORM i relacje na bazie. Grupuj w obiekty, tylko te dane, które zmieniają się razem i ich spójność natychmiastowa jest konieczna do spełnienia reguł biznesowych — czyli decydują czy jakaś operacja jest możliwa i czy wynikowe zdarzenie ma prawo zaistnieć. Dzięki temu otrzymasz bardziej wyspecjalizowane modele o niższej złożoności. Nie spowodujesz też przez optimistic locking sytuacji gdzie np. zmiana nazwy pokoju, wywłaszcza aktualizację z nową rezerwacją -> a tym samym Twój biznes traci pieniądze.

🦄 Bounded Context wjeżdża cały na biało

Zakładając, iż model łączy w sobie dane i reguły, to czy we wszystkich kontekstach (jak np. Rekrutacja, Walka, Ulepszanie) potrzebujemy taki sam model jednostki? Już przecież wiesz, iż nie. Każdy z nich będzie miał inne reguły i potrzebne do ich sprawdzenia zestawy danych też będą się różnić. Tylko ID będzie takie samo. Czyli na poziomie rozwiązania będziemy mieć różne klasy Creature, na których będzie można wykonać inne operacje. Aby zdecydować, czy dana operacja może być wykonana, nie potrzebujemy wszystkiego. Nie wyznaczaj klas na podstawie nazw, czy rzeczowników. Szukaj reguł spójności — co się zmienia razem, a co nie.

Dzięki temu uzyskasz też niski coupling i wysoką kohezję (a o tym pisał już choćby Uncle Bob w Clean Code). Pamiętaj oczywiście, iż to nie są cele same w sobie. Strukturyzujemy kod, żeby go łatwo zrozumieć. Kiedy rozumiemy, to możemy gwałtownie wprowadzić zmianę i uniknąć bugów. Do tego sprowadza się na koniec dnia praca programisty. Jeśli nie jesteś pewien(-na), co słówko kohezja (ang. cohesion) znaczy, to jest to dobry czas na przypomnienie. Jednak pamiętasz? To dobrze dla Ciebie i twoich współpracowników!

Do zapewnienia niezmienników w danym kontekście, nie potrzebujemy wszystkich możliwych danych, o konkretnym rzeczowniku.
(Kliknij obrazek, aby powiększyć)

Powyżej widzisz możliwy podział modelu jednostki. Analogiczny obrazek (bazujący na częściach świni) w książce Patterns, Principles, and Practices of Domain-Driven Design zmienił moje programistyczne życie już na zawsze.

Co nas interesuje w danych kontekstach?

  • Bitwa (ang. Combat): działające czary, szczęście, morale, punkty życia.
  • Rekrutacja (ang. Recruitment): koszt, dostępność, przyrost (zależy od budowli w mieście i np. symbolu tygodnia).
  • Podobnie, gdy spotykamy wrogą jednostkę na mapie, czy istotne jest, do którego gracza ona przynależy, albo czy można ją ulepszyć? Oczywiście, iż nie.

Jeśli weźmiemy do każdego zastosowania model jednostki z mapy (może tutaj sprawdzi się generyczny “shared”), będzie on zbyt prosty i nie spełni wymagań. Za to w drugą stronę będziemy mieli niepotrzebne skomplikowane i kod będzie dłuższy, nafaszerowany ifami a w efekcie trudniejszy do zrozumienia i wprowadzenia zmiany. Jednak skąd wiedzieć, iż akurat te atrybuty powinny być razem? Skąd wziął się ten podział? Jak sobie poradzić z tyloma polami na raz? Odpowiedź brzmi: nie radzić. Lepiej pójść w drugą stronę i pozwolić, aby to potrzebne atrybuty wynikały z pożądanych zachowań systemu.

🤖 (Dane ∪ Zachowania) ⊂ Model

Patrzenie na system tylko z perspektywy danych i pominięcie zachowań sprawia, iż nasze rozwiązanie będzie kulawe. Szczególnie kiedy mamy do czynienia z rozbudowaną logiką biznesową, a nie jedynie przeglądarką do bazy danych. Dominująca perspektywa BEING objawia się plątaniną ifów, z której ciężko się wygrzebać. Więcej na ten temat posłuchasz w tych odcinkach podcastu Better Software Design od Mariusza Gila:

Niestety wciąż, choćby osoby z wieloletnim doświadczeniem, kiedy prosi się je o “zaprojektowanie architektury” rysują prostokąty, łączą je strzałkami i zadowoleni uznają planowanie za zakończone. Jednak dopiero kiedy nałożymy na taki diagram zachowania i proces biznesowy (zdarzenia i komendy — o tym zaraz!), to wychodzi na jaw skrywana w tajemnicy plątanina powiązań i wzajemnych zależności. Mikroserwisy (buzzword alert!) nie uchronią Cię przed tym problemem, a co gorsza — choćby go pogłębią!

💸 Autonomiczne modele i produkty

Nadrzędna zasada, o której już słyszałem w liceum, a nie zauważyłem, jak jest potężna: dziel i zwyciężaj! Dlaczego to takie ważne? Jak nie wiadomo, o co chodzi, to chodzi o pieniądze. A właśnie po to robimy nasz software - żeby zarabiał.

Twój model albo umożliwia szybkie pivoty, albo nie. Twój model albo oferuje możliwość nieplanowanej produktyzacji modułów, albo nie.

~ DomainDrivers.pl, Sławomir Sobótka i Jakub Pilimon

Brutalne, ale prawdziwe. Nie ma nic pomiędzy. Czy dany moduł mógłby działać samodzielnie i wnosić wartość? Czy możesz zrobić Proof of Concept nowego produktu, nie rozwalając całego systemu naokoło? Standardowo odpowiedź brzmi: nie, gdy brak jest jasno wyznaczonych granic części systemu. Ale gdzie są te dolary?

🚗 Moduł = Produkt #1: Uber

Takie rozwiązania stosują też najwięksi gracze na rynku. Gdyby system taki jak Uber, nie odseparował wyznaczania drogi i przejazdu od samego przewozu osób, to nie mógłby z łatwością wprowadzić możliwości dostawy paczek przez swoją aplikację. W tym przypadku architektura aplikacji jest właśnie (nawiązując do Heroes) prawdziwą kopalnią złota. Dzięki komponowaniu Capabilities (możliwość, jakie ma nasz biznes, np: wyznaczenia drogi i floty samochodów) mógł powstać nowy produkt na bazie istniejących rozwiązań, ale bez zmieniania innych części systemu, jak np. przewozy osób.

🎲 Moduł = Produkt #2: Heroes III Board Game

Heroes III Board Game — Battlefield expansion. Dodatkowy produkt, który umożliwia Ci alternatywny sposób prowadzenia walki.
(Kliknij obrazek, aby powiększyć)

Gra planszowa Heroes III umożliwia dwa sposoby prowadzenia bitwy:

  1. Prostszy, dostępny w wersji podstawowej i bazujący na kartach. Dostępny w wersji podstawowej.
  2. Bardziej rozbudowany, toczący się na specjalnej planszy z figurami jednostek. Wierniej odwzorowujący mechanikę z gry komputerowej. Możliwy do dokupienia.

Dzięki odpowiedniej modularyzacji i odseparowaniu sposobu toczenia się walki od reszty elementów oraz elastyczność zasad (czyli procesów w grze) umożliwione zostało proponowanie dodatkowych produktów dla nowych i aktualnych klientów. Podobne praktyki można zastosować w softwarze. Już Heroes V umożliwiały też toczenie walki między bohaterami nie tylko w trybie scenariusza.

👨‍💻 Programista = partner biznesowy?

Jako programista możesz nie być tylko “wykonawcą”, których prawdopodobnie zastąpi AI, ale realnym partnerem biznesowym, otwierającym nowe możliwości. Projektantowi systemu płaci się za to, żeby wprowadzanie potencjalnych zmian i nowych funkcjonalności nie było zbyt kosztowne.

Nie raz w zespołach produktowych padają ustalenia: “zamiast prowadzić dyskusje bez końca, róbmy Proof of Concept”. Potem i tak nikt tego nie realizuje, bo nie umożliwia tego stan naszego systemu. Winą nie jest mityczny “dług technologiczny” (Technical debt isn’t technical - koniecznie zobacz tę prezentację!) rozumiany jako np. używanie zamierzchłej technologii, ale właśnie poplątany model.

Twoja firma kolejnym unicornem? Nie tylko dzięki Twojej pracy, ale oby nie pomimo niej.

Podział i modularyzacja systemu to nie jest fanaberia programistów, ale musi iśc w parze z dogłębnym zrozumieniem domeny biznesowej. Kiedy modelujesz, stawiaj sobie przed oczami nadrzędny cel, jakim są autonomiczne moduły. To, w jaki sposób je zaimplementujesz, jest kwestią drugorzędną. Jak więc dzielić, aby zwyciężać? Sprawdź część drugą tego wpisu, gdzie: wykonamy Event Modeling, model przełożymy na kod i zapewnimy odpowiednią jakość jeszcze przed code review!

  • ⚔️ Moc i magia Domain-Driven Design w świecie Heroes III: Event Modeling, stawianie granic i wysoka jakość bez code review

♞ Dlaczego Heroes III?

Chcę Ci pokazać: mój obecny stan wiedzy, w jaki sposób realizuję projekty i mój tok myślenia, a także warsztat praktyk inżynierskich. W szczegóły stosowanych metod będę wchodził w osobnych wpisach.

Na potrzeby dydaktyczne, choć domena jest dosłownie fantastyczna, to o wiele jej bliżej do rzeczywistych projektów, niż przykładom, jakie zobaczysz na wielu konferencjach, w stylu “kino” czy wałkowany przez wiele książek “ecommerce”. Każde kino i koszyk działa inaczej, więc gdy to my wymyślamy wymagania biznesowe, wtedy kształtujemy nie tylko model, ale też rzeczywistość. Tutaj już jest ona określona poprzez zasady gry, a my musimy ją odkryć i adekwatnie zamodelować — jak w realnym projekcie.

Zapraszam Cię do tej kampanii, w której będziemy razem modelować świat Heroes III w oparciu, o praktyki takie jak DDD i Event Modeling. Czy taka “zabawa” ma naprawdę sens?

❤️ Inni też tym żyją

Na sam koniec przytoczę cytat, który idealnie tłumaczy cel tego wpisu i zmotywował mnie do jego ukończenia (draft powstał 2 lata temu)! Pochodzi z kursu DomainDrivers.pl, prowadzonego przez Sławka Sobótkę i Jakuba Pilimona.

Często w proces modelowania wkracza właśnie taki niespodziewany błysk intuicji, a ktoś z boku ma wrażenie wyjmowania królika z kapelusza. Ta do końca jeszcze niewyjaśniona zagadka intuicji jest najpewniej nieuświadomioną wiedzą, która pochodzi z doświadczenia. I trzeba jawnie przyznać, iż intuicja sprzyja tym, którzy mieli okazję eksponować się na różne przypadki. Rozwiązanie wtedy jest zaskakującym skojarzeniem dwóch tak odległych dziedzin, iż logiczny tok myślenia nie zawsze je łączy. Czyli im więcej przypadków widzisz, tym lepiej modelujesz. Ale uwaga, nie musisz zmieniać pracy, żeby te przypadki widzieć, obserwować i eksponować się na nie. Wystarczy po prostu obserwować nadmiar technologii, której na co dzień używamy i modelować to, czego używamy, ale w głowie. Takiego DDD nikt w swojej organizacji nie zabroni ci stosować.

Chcę wraz z Tobą budować tę intuicję, abyś gdy w rzeczywistym projekcie spotkasz analogiczny przypadek, Twój mózg od razu wiedział, jak zareagować.

Jeśli chcesz w tym czynnie uczestniczyć, to zapisz się na moją listę mailingową TUTAJ.

A tymczasem przejdźmy do kolejnej części, gdzie wykonamy Event Modeling!

  • ⚔️ Moc i magia Domain-Driven Design w świecie Heroes III: Event Modeling, stawianie granic i wysoka jakość bez code review
Idź do oryginalnego materiału