Od kolorowych karteczek po implementację kodu. Autostopem przez Domain Driven Design
Jeszcze kilka lat temu, nikt nie przypuszczał, że coś takiego jak blockchain będzie w ogóle istnieć, a machine learning będzie tak łatwo dostępne. To wszystko zbudowane jest z mniejszych elementów, których budowa zajęła ludzkości kawałek czasu. Budujemy coraz większe i bardziej złożone systemy. Jak podejść do tematu projektowania rozwiązań informatycznych (głównie w kwestii software development), tak, by w pewnym momencie nie stwierdzić, że aplikację należy napisać ponownie?
Rafał Kozłowski. Senior Software Engineer oraz Team Lead w Unit4. Posiada grubo ponad 10 lat profesjonalnego doświadczenia, zbliżającego się nieubłaganie do 20. Pracuje głównie w technologiach .NET (zarówno Framework jak i Core) oraz jest wielkim sympatykiem Angulara i architektury używającej mikroserwisy. Zagorzały zwolennik Domain Driven Design, Command Query Responsibility Segregation oraz Single Page Application.
Niejednokrotnie takie pisanie “od zera” może sprawić, że aplikacja, a w najgorszym wypadku firma, upadnie. Wystarczy spojrzeć choćby na przeglądarkę Opera, w której w pewnym momencie została podjęta decyzja o przepisaniu. Firma nie upadła, ale udział przeglądarki w światowym rynku znacząco spadł. Niestety często się tak dzieje, aczkolwiek nie często świat o tym słyszy.
Spis treści
Oprogramowanie ma służyć biznesowi
W internecie można znaleźć tysiące stron, poradników, samouczków, filmów i innych materiałów, które przekazują wiedzę jak budować aplikacje w oparciu o podejście DDD — Domain Driven Design. Mało które jednak zwracają uwagę na to, że DDD nie jest paradygmatem (albo frameworkiem, metodyką) programowania. Jest to raczej całościowe podejście do wytwarzania oprogramowania, począwszy od zebrania wymagań od klienta (lub eksperta domenowego), poprzez dokładne zrozumienie co ma się w oprogramowaniu dziać (poprzez Event Storming, zarówno Big Picture jak i kolejne), a następnie poprzez odpowiednie, architekturalne tworzenie oprogramowania, by w przyszłości nie spotkała nas sytuacja jak z Operą.
Podstawowe założenie DDD jest takie, że oprogramowanie ma służyć biznesowi. Więc to co biznes chce, to musi dostać (aczkolwiek nie ukrywajmy — pewnych rzeczy pożądanych przez biznes po prostu nie da się osiągnąć). Przy standardowym podejściu do wytwarzania oprogramowania, programista dostaje projekt i zaczyna budować aplikację. Najfajniejsza dla niego zabawa to stworzenie całej infrastruktury, klas bazowych i wyszukanych technologicznych rozwiązań, dzięki którym będzie mógł powiedzieć “patrzcie jak to genialnie zrobiłem, jestem z siebie dumny”.
Wiem jak to działa, sam jestem inżynierem oprogramowania, lubię siedzieć w technikaliach i wymyślać jakieś ciekawe rozwiązania. Bardzo często, przez to, że jako technicy lubimy rozwiązywać techniczne problemy, zapominamy o… biznesie. Po czasie okazuje się, że nasze super hiper mega rozwiązanie, z którego byliśmy tacy dumni jeszcze kilka tygodni temu — teraz nie zadowala biznesu i opóźnia ukończenie aplikacji. No ale przecież skoro coś tak pięknego zostało zrobione, to nie możemy tego wyrzucić — zwłaszcza, jak siedzi to na niskim poziomie w aplikacji, to nie da się tego zrobić w sensownym czasie.
D jak Design
Po co wytwarzamy oprogramowanie? Po to, żeby dostarczyć wartość klientowi końcowemu. I na tym się głównie skupia podejście oparte na DDD. Wbrew temu co wielu programistów mówi, ostatnie D w tym akronimie nie wywodzi się od “development” tylko “design”. Jak widać, przeniesiony jest ciężar z programowania na projektowanie. Przy dobrym wdrożeniu Domain Driven Design, kod aplikacji jest poniekąd skutkiem ubocznym, nie celem samym w sobie. Celem jest dostarczenie wartości biznesowi (klientowi) poprzez wzajemne dobre zrozumienie tego, co jest oczekiwane.
Jak więc to uzyskać? Po pierwsze i najważniejsze, to kwestia wzajemnego zrozumienia pomiędzy biznesem i osobami technicznymi. Jak często bywa tak, że biznes mówi o czymś, a technicy rozumieją to inaczej? Albo rozumieją to w innym kontekście? W DDD uznaje się, iż język jest wszechobecny (ubiquitous language) — co przecież jest prawdą. Ważne jest zatem, by dobrze się rozumieć na poziomie języka i ramach danego kontekstu, dobrze danych sformułowań używać. Przykładem może być coś, co nazwiemy “użytkownik”.
W kontekście konta systemowego, może być zdefiniowane jako “osoba, która ma imię, nazwisko, login i hasło”. Czy “użytkownik” będzie taki sam w innych kontekstach? Na przykład w kontekście działu finansowego firmy? Raczej nie, w tym kontekście, będzie to raczej opis “osoba, która ma numer identyfikacyjny, numer konta księgowego, listę dostarczonych i otrzymanych faktur, listę wpłat jakich dokonał oraz listę wysłanych płatności”. Już na pierwszy rzut oka widać, że w różnych kontekstach, ta sama nazwa może mieć różne znaczenia. I tutaj wchodzi DDD.
Wspólny język
Zgodnie z założeniem, na każdym spotkaniu ustala się wspólny język lub korzysta z wcześniej ustalonego. Ale ważnym jest, by właśnie tą wspólną część mieć. Dzięki temu, gdy jest spotkanie w kwestii dorobienia feature do modułu księgowego, gdy biznes powie “użytkownik”, każdy z uczestników spotkania będzie znał doskonale to, o czy mowa i nie zrozumie tego jako użytkownik w kontekście na przykład logowania. Podobne odzwierciedlenie musi być w kodzie aplikacji. Ważnym wspomnienia jest to, by tworząc oprogramowanie, również używać sformułowań, ustalonych podczas spotkań z klientem lub biznesem. Tak, by nie było rozbieżności, bo to ona przyszłościowo może generować problemy, które sprawią, że jedyne co można zrobić z aplikacją to ją przepisać, a tym samym ryzykować zakończenie całego projektu porażką. Utrzymując spójność, łatwiej odnaleźć odpowiednie kawałki kodu, jak i rozmawiać na spotkaniach, bo wszyscy, na każdym etapie i poziomie używają tego samego języka.
Kiedy obie strony zrozumieją, że język jest najważniejszą częścią całego procesu, reszta zaczyna iść łatwo. W przypadku standardowo prowadzonego projektu, spotkania zdarzają się dość często są to refinement’y (bądź groomingi), bądź jakiekolwiek uszczegółowienia. Mogą też być spotkania wynikające ze zmieniającego się podejścia lub odkrycia czegoś, co uniemożliwia użycia takiego a nie innego, wcześniej obranego rozwiązania. Podobnie jest w przypadku Domain Driven Design — spotkania mają być organizowane wtedy, kiedy są potrzebne. Dlaczego? Dlatego, że język i zrozumienie są najważniejszą częścią całego procesu wytwarzania oprogramowania. Gdy wszystko dobrze się zgra, to korzystając z podejścia przedstawionego przez Eric’a Evans’a, kod powstaje sam i jest zgodny z wymaganiami.
W przypadku DDD, spotkania mają trochę inną formę. Nie jest to stricte omówienie potrzeb klienta, jest to raczej ciągła dyskusja prowadząca do wyjaśnienia nieścisłości. Aplikacje projektowane są w taki sposób, by osoby nietechniczne zrozumiały cały proces i mogły w nim aktywnie uczestniczyć — takie spotkania nazywane są Event Storming.
Jak się spotykać
Wyobraźmy sobie sytuację, że mamy standardowe podejście do procesu wytwarzania oprogramowania i nie udało się czegoś omówić — standard, tak się zdarzało i będzie zdarzać. Twój manager / ekspert domenowy (na przykład główny księgowy, kiedy omawiany i rozwijany temat dotyczy księgowości) jest osobą niezwykle zajętą. Ciężko taką osobę codziennie wyciągać na półgodzinne spotkanie “bo coś wyszło”. Organizując spotkania typu Event Storming, takie problemy występują rzadziej. Jak taki ekspert domenowy (czyli osoba, która zna dane zagadnienie od strony biznesowej) znajdzie czas, to staramy się wykorzystać tę szansę maksymalnie, by uzyskać wszystkie odpowiedzi na pytania. Nawet te, które mogą powstać w przyszłości.
Do poprawnego przeprowadzenia takiego spotkania potrzebujemy:
1. Eksperta lub ekspertów domenowych (biznes/klient/ktoś kto ma wiedzę o danym zagadnieniu) oraz zespół.
2. Rolka papieru o szerokości 1m i długości 10m.
3. Kolorowe karteczki, tzw. sticky notes (potrzebne kolory to: pomarańczowy, niebieski, fioletowy, różowy, żółty).
4. Długopisy lub markery.
Oczywiste jest, że spotkanie będzie pomiędzy zainteresowanymi stronami, więc punkt 1 można pominąć z omówienia. Ciekawie zaczyna się przy rolce papieru o podanej szerokości i długości. Po co to? Patrząc na pozostałe punkty (3 i 4) można dojść do wniosku, że rolka ta jest potrzebna, by przyklejać na nim karteczki, na których coś zostanie zapisane. I dokładnie tak będzie. Rolka papieru jest użyta z dwóch powodów. Po pierwsze, sticky notes lepiej się przyklejają do papieru niż do tablicy lub ściany i dłużej się trzymają. Po drugie, rozwiniętą rolkę, nawet z naklejonymi karteczkami, można zwinąć i przenieść w inne miejsce.
Rozróżniamy dwa rodzaje spotkań typu Event Storming — pierwszym jest Big Picture, na którym omawiamy główne założenie projektu bądź aplikacji. W wyniku tego spotkania uczestnicy wiedzą ogólnie o co chodzi w projekcie i jakie są domeny projektu (poprzez domeny rozumiemy osobne części, które działają niezależnie od innych — bardzo często podział per domena odpowiada podziałowi na działy w organizacji — dział kadr, finansowy, support, r&d i inne). Takich ogólnych spotkań może być kilka. Nie jest przecież powiedziane, że po pierwszym spotkaniu poziom ogólności zostanie wyczerpany i będziemy przechodzić od razu do szczegółów.
Na szczegółowych spotkaniach omawia się już poszczególne domeny bądź konteksty (kontekst to mniejsza jednostka w ramach domeny, dla przykładu: w dziale mogą być konkretne zespoły, a zespoły mogą się dzielić na poszczególne osoby). Poza tym niczym nie różnią się od spotkań Big Picture.
Aby omówić jak wygląda samo spotkanie, muszę jeszcze omówić kolory karteczek — określić jakie rzeczy można pisać na danych kolorach.
Kolory sticky notes:
1. Pomarańczowy — zdarzenie (event) na takiej kartce zapisujemy zdarzenia jakie będą dziać się w aplikacji. Na przykład: “użytkownik zarejestrowany”, “faktura wystawiona”, “opłacono fakturę” i tym podobne. W tym kontekście używamy czasu przeszłego, gdyż zdarzenia, a dokładnie informacja o zdarzeniu jakie zostało wykonane, zawsze wysyłana jest po fakcie. Na tym etapie piszemy językiem naturalnym, tak, aby każdy doskonale to rozumiał (znów ten ubiquitous language). Każdy członek ma swoje kartki i zapisuje co, w kontekście zdarzeń, dzieje się w aplikacji. Po tym jak kartka zostanie zapisana, należy ją przykleić do naszej rozwiniętej rolki papieru. Taką rolkę przyczepiamy na ścianie, by każdy miał do niej dobry dostęp. Na samym początku naszych spotkań skupiamy się na zdarzeniach (eventach) — i tylko takie karteczki przyklejamy. Gdy uznamy, że jest ich już wystarczająco dużo, bo obrazują flow jakie zachodzi w aplikacji bądź domenie, możemy przejść do kolejnego koloru.
2. Niebieski — decyzja/akcja (decision/action) — tutaj zapisujemy jakie komendy wywołane w systemie powodują wygenerowanie zdarzenia (eventu, kolor pomarańczowy) w systemie. Na osi czasu występują po lewej stronie zdarzeń. Od tego momentu możemy też zacząć segregowanie zdarzeń wraz z odpowiadającymi komendami na osi czasu.
3. Żółty — użytkownik / encja (user / aggregate root) — określa co wywołuje komendę. Znajduje się po lewej stronie kartki w kolorze niebieskim. Jeśli użytkownik — kartka jest żółta. Jeśli komenda wywołana jest przez klasę / aggregate root, kolor może być bladożółty.
4. Fioletowy — niewiadoma (puzzle) — na takich karteczkach zapisywane są wszelkie rzeczy do rozwiązania na później. W momencie pisania karteczki nie mamy na przykład wiedzy czy zewnętrzny system na 100% dostarcza nam dane, jakich potrzebujemy. Jest to notatka, którą trzeba mieć na uwadze.
5. Różowy — zewnętrzne systemy (externals) — na takich kartkach zaznaczam dostęp do zewnętrznych systemów, na przykład PayPal, eBay albo inne.
Po zapisaniu kartek, kiedy widzimy już co ma się dziać i kiedy, możemy pogrupować elementy systemu w bounded contexts i poustawiać je na osi czasu (jeśli jeszcze tego nie zrobiliśmy). Domenę możemy określić jako dział w organizacji, a bounded context — jako zespół w dziale. Czyli określając bounded context, niejako wyznaczamy, który zespół ma się zajmować generowaniem komend i, w rezultacie, zdarzeń — czyli zmian w danych.
Core domain
W ramach tego grupowania, możemy (i nawet powinniśmy) wydzielić “core domain”, czyli te części systemu, które są najważniejsze dla organizacji. Najważniejsze pod kątem wartości, czyli to co definiuje główny biznes. Wszystko inne jest zaliczane do tak zwanych “sub domains”, czyli domen wspomagających. Dla przykładu dział pocztowy w organizacji zajmującej się wytwarzaniem oprogramowania, raczej nie będzie zaliczony do core domain. Jest to dział wspomagający odpowiedzialny na przykład za masową wysyłkę drukowanych faktur do klientów.
Gdy zakończymy ten etap, obecność biznesu przestaje być wymagana. Trzeba jednak pamiętać, że oprogramowanie tworzymy dla nich, więc im częściej się z nimi spotykamy, tym lepiej — aczkolwiek przeważnie są zabiegani, dlatego tak ważne jest wspólne zrozumienie i ustalenie mapy. Dobrze skonstruowana mapa jest niemalże odzwierciedleniem kodu źródłowego — dlatego przy poprawnie przeprowadzonym procesie, kod jest niejako efektem ubocznym.
Event Storming powinno trwać od kilku minut do kilku godzin, nie ma limitów. Kiedy dociera się do momentu, kiedy nikt już nic nie dodaje, warto zrobić przerwę do następnego dnia i ponowić sesję. Kolejnego dnia może się bowiem okazać, że coś co już ustaliliśmy finalnie może być niepotrzebne, podobnie jak możemy sobie zdać sprawę, że zapomnieliśmy o takich, a nie innych elementach systemu. Jak już zbierzemy dostatecznie dużo informacji, możemy przystąpić do najfajniejszej dla każdego programisty części — implementacji. Przy dobrze przeprowadzonym procesie, nasz kod jest odzwierciedleniem karteczek, które zostały przyklejone do naszego płótna. Dla przykładu “użytkownik wysyła żądanie rejestracji” -> “obsługa żądania rejestracji” -> “obiekt ‘rejestracja’ został utworzony”.
Big Ball Of Mud
Domain Driven Design, pomimo tego, że nie jest nowością, w wielu organizacjach traktowane jest po macoszemu. Wiele osób nawet nie wie, że coś takiego istnieje. Jeszcze inne wiedzą, ale uważają, że im to nie jest potrzebne. Prawda jest taka, że firmy (i ludzie) dzielą się na dwa rodzaje — takie, które już wiedzą czym jest DDD i je stosują i takie, które wcześniej czy później będą używać tego podejścia do wytwarzania oprogramowania.
Wynika to wprost z tego, że każda, nawet najmniejsza aplikacja w miarę upływu czasu i zdobywania rynku, rośnie. To powoduje coraz więcej logiki biznesowej upchanej w systemie. Jeśli tego się zawczasu nie uporządkuje, będzie to prowadzić do zamieszania w kodzie i w rezultacie do tzw. spaghetti code (w DDD, taki kod nazywa się Big Ball Of Mud). Im wcześniej zatem zacznie się wprowadzać dobre praktyki do procesu wytwarzania oprogramowania, tym lepiej.
Oczywiście ta metoda pracy nie sprawdza się do wszystkiego. Na przykład jeśli aplikacja (lub moduł) służy tylko do edycji danych słownikowych w systemie, to bez sensu jest zaprzęgać do tego DDD. Powodem jest narzut zarówno od strony spotkaniowej (design) oraz od strony programistycznej — zbyt dużo kodu infrastrukturalnego jest to wytworzenia. DDD dobrze sprawdza się tam, gdzie logika biznesowa jest złożona lub w przyszłości wiemy, że taka będzie. W przypadku rzeczy niesłychanie prostych, tą metodę pracy można przyrównać do użycia armaty do zabicia komara.
Zdjęcie główne artykułu pochodzi z stocksnap.io.