poniedziałek, 9 marca 2009

Undergroundowy Domain Driven Design

"Kraków - dawna stolica Polaków"

W sobotę odbyła się w Krakowie konferencja 4develoeprs, na której to byłem miałem (że pozwolę sobie napisać w staropolskiej składni zaprzeszłej będąc natchnionym pobytem w mieście poetów i masakrycznych dziur w asfalcie) przyjemność przedstawić krótką prezentację.

Tym, którzy zastanawiają się jakim cudem pojawiłem się na salonach obok takich znamienitości jak Adam Bien czy Neal Ford śpieszę wyjaśnić iż chodzi o Java Underground.

JU, którego organizatorem był Grzegorz Duda polegał (w skrócie) na przedstawieniu prezentacji w ciągu 5 minut, a brali w nim udział: Adam Bien (EJB), Bartek Kuczyński (JavaFX), Szymon Jachim (Scala), Bartek Chrabski (Jazz) no i ja (o Jacku L. który nie dotarł nie wspomnę;)

Aby zmieścić się w konwencji jako temat wybrałem sobie pewne "undergroundowe" aspekty architektury pojawiające się w DDD. Jako, że temat spotkał się z pewnym zainteresowaniem, a ja w ciągu pięciu minut byłem w stanie wydobyć z siebie jedynie turbo-bełkot, rozwinę w tym poście nieco bardziej każdy ze slajdów.



O ile samo podejście DDD nie jet już undergroundem to implikacje wynikające z podążania za nim można już do undergroundu zaliczyć. Ortodoksyjne przestrzeganie zasad DDD prowadzi do pojawienia się na poziomie projektu i implementacji pewnych konstruktów, które mogą się wydawać obrazoburcze, szczególnie w porównaniu z tradycyjnym podejściem, lansowanym przez różnego rodzaju frameworki w różnego rodzaju tutorialach.



No więc właśnie... silnie zakorzeniona tradycja każe umieszczać algorytmy w procedurach a dane w strukturach danych.



Źródła tej tradycji sięgają co najmniej szalonych lat '70 (pewnie i wcześniej), kto programował w turbo pascalu ten zapewne widzi analogię pomiędzy servisami a procedurami oraz encjami a rekordami.

Tradycja proceduralna jest mocno ugruntowana i nie można jej kwestionować. Doskonale nadaje się do modelowania tych domen, które z natury są proceduralne. Niestety nie sprawdza się zbyt dobrze w przypadku problemów z domen o naturze obiektowej.

Warto zastanowić się dobrze w początkowej fazie projektu nad naturą domeny. Nie warto walczyć z naturą - nikt jeszcze tej walki nie wygrał. Wszyscy wiemy czym kończy się proceduralne podejście stosowane do skomplikowanych (nie znaczy dużych!) domen... Prędzej czy później pojawia się ON. Wyłania się z plątaniny zależności i powielonej logiki... Przeklęty demon... Potwór Spaghetti.

Właściwie to w klasycznym podejściu będzie to Potwór Lasagne (Spaghetti z warstwami;)



Koncepcja DDD pojawiła się jako odpowiedź na problemy z komplikacją logiki biznesowej. DDD zakłada, że warstwa logiki domenowej jest najważniejszym elementem systemu i jednocześnie najtrudniejszym do opracowania. Jest to serce systemu, ale serce bardzo kruche. Niepoprawny model nie jest odporny na zmiany (której jak wiemy z doświadczenia są pewne jak śmierć i podatki - podatki to chyba nie u nas).

DDD nie jest żadną technologią, platformą ani frameworkiem. Jest to jedynie zbiór zasad, wytycznych, wzorców i dobrych praktyk, których stosowanie ma zapewnić zaprojektowanie porządnego modelu domeny.

DDD to dosyć obszerna "filozofia", natomiast na potrzeby przykładów undergroundowych architektur zobaczymy tylko mały jej fragment. Przy okazji warto przypomnieć, że DDD aplikuje się raczej do złożonych problemów. Pytanie tylko, kto potrafi przewidzieć gdzie zabrnie projekt - już parę razy spotkałem się z projektami, które na początku wydawał się "szybką akcją bydgoskiej milicji". Nota bene: proste problemy powinno rozwiązywać się chyba w Excelu; ostatecznie w Accesie;) Ale jeżeli już czujemy silną wewnętrzną potrzebę aby wydurniać się z programowaniem prostych problemów i nie chcemy robić tego proceduralnie to zawsze można skorzystać z podejścia DDD Lite.



DDD tak na prawdę nie jest niczym nowym. To po prostu zestaw dobrych praktyk opartych na starych dobrych (ale nie tak starych i nie tak dobrych jak Turbo Pascal;) koncepcjach obiektowej analizy i projektowania. Wystarczy po prostu ich nie negować i nie programować wbrew nim - a jest to konieczne aby osiągnąć pewien cel...

DDD zakłada unifikację modelu analitycznego i projektowego, o którym już pisałem: UP-DDD in Action: Malutka Teoria Unifikacji. Cały zespół, począwszy od analityków, poprzez projektantów aż po koderów powinien posługiwać się wspólnym językiem opisującym domenę - zwanym w DDD Ubiquitous Language. Niestety aby było możliwe dosłowne zaprojektowanie pomysłów analityka musimy posługiwać się pełnym wachlarzem środków wyrazu języków OO. Procedury i encje nie wystarczą - o tym dalej przy okazji archetypów.




DDD zakłada modelowanie warstwy logiki domenowej przy użyciu pewnych standardowych klocków, zwanych Building Blocks. Jest to jedna z głównych koncepcji. Na pierwszy rzut oka może się wydawać, że jest ich całkiem dużo, ale każdy ma uzasadnienie swego istnienia wynikające z koncepcji Responsibility Driven Design, o której pisałem.



Artefakty już opisywałem, więc jedynie gwoli przypomnienia:

  • Encje w DDD nie są tym samym czym czym w standardowym podejściu. Encje modelują biznes więc posiadają odpowiedzialność - metody biznesowe.
  • Servisy w DDD nie są tym samym czym czym w standardowym podejściu. Serwisty z tej warstwy enkapsulują logikę, której nie można w sensowny sposób przypisać do odpowiedzialności żadnej z encji.
  • Repozytorium jest abstrakcją persystencji - pamiętajmy, że w nietrywialnych projektach części agregatów (grafów encji) mogą pochodzić z różnych źródeł (np część z bazy, część z web servisu, a inna część z LDAP)

W ortodoksyjnym podejściu encje nie posiadają nawet getterów i setterów. To jest totalny underground w porównaniu z klasyką. Niewielu programistów potrafi to zaakceptować i dla tego chowają się za murem dysonansu kognitywnego.

Pewnie zastawiasz się: jeżeli nie ma nawet getterów/setterów to w jaki sposób wyświetlić encję na formularzu? Albo jak ją zmienić? Odpowiedź już niedaleko...



Nieograniczanie się do anemicznych (pozbawionych odpowiedzialności) encji oraz prymitywnych servisów ma kolejną niebagatelną zaletę. Posługiwanie się bogatym zestawem Building Blocks oraz korzystanie z bogactwa technik OO pozwala niemal dosłownie implementować zaawansowane i złożone modele analityczne. Na przykład modele wyprowadzone z archetypów. O archetypach już w niedalekiej przyszłości pojawię się parę postów na tym blogu, ponieważ IMHO jest to zdecydowanie ważniejsze niż kolejny gówniany framework webowy.

Póki co przyjrzyjmy się przykładowemu modelowi użytkowników na powyższym slajdzie... Piękny, prawda? Niestety nadaje się jedynie na przykłady w książkach lub jako model w projektach "Hello World".



Chcąc nieco bardziej profesjonalnie zaprojektować model np. użytkowników możemy
a) pomyśleć - co nie gwarantuje sukcesu
b) skorzystać z archetypu o nazwie "party" ("uczestnik", nie "impreza")

Tak jak wspomniałem o archetypach będzie później, ale zwróćmy uwagę na (pozorną) komplikację powyższego modelu. Jest fajny, ponieważ można w nim przedstawić dowolną relację pomiędzy dowolnymi uczestnikami (np ja mogę być klientem jakiejś firmy jak i jej pracownikiem; firma ta może być klientem mojej żony; itp).

Ale życzę powodzenia implementując ten (czy inny) zaawansowany archetyp przy pomocy klasycznego podejścia - anemicznego modelu. Widzicie już oczyma wyobraźni te łańcuszki 15 kropek i getterów - train wreck.

Z wykorzystaniem technik DDD możemy w sposób naturalny (OO) i bezproblemowy zaimplementować ten model a całą komplikację ukryć w hermetycznym agregacie (grafie encji), który operuje na swej wewnętrznej skomplikowanej strukturze poprzez metody biznesowe.



Undergroundowe w DDD jest również podejście do zależności. Otóż zależności na poziomie tej samej warstwy nie są niczym złym. Przecież zależności odzwierciedlają model biznesu, więc nie ma potrzeby aby się wydurniać z wstrzykiwaniem zależności czy tworzeniem na siłę interfejsu dla każdego komponentu biznesowego. Wiem, że niektóre platformy od niedawna;) obsługują Wstrzykiwanie Zależności i wymagają wszędzie interfejsów, ale do wszystkiego trzeba podejść z rozsądkiem.

Natomiast zależności modelu domenowego od klas infrastruktury są niepożądane. Nawet gdy klasy te są przykryte interfejsem - to nic nie zmienia, może co najwyżej poprawia nam samopoczucie.

Jedyna techniczną zależność modelu biznesowego jaką dopuszcza DDD jest zależność od szyny komunikatów. Obiekt biznesowy w momencie, gdy poczuje taką potrzebę może co najwyżej stworzyć komunikat i puścić go do pipe (która zawiera zwykle filtry). Jest to architektura pipeline.



Klasyczne podejście warstwowe również nie sprawdza się zbyt dobrze w przypadku DDD. Na powyższym slajdzie widzimy 2 możliwości:

Możemy "podpinać" obiekty biznesowe (te, które mają jakiś stan) pod GUI. Niestety wiąże się to z pewną niezręcznością: GUI "widzi" metody biznesowe. Poza tym obiekty biznesowe muszą oferować gettery i settery w celu dokonywania modyfikacji na nich. Niestety narusza to obiektowy paradygmat enkapsulacji, czyli prowadzi do pornografii obiektowej - odkrywania wewnętrznych struktur.

Podejście to ma jeszcze jedną wadę. Implementowanie setterów pozwala na istnienie obiektu w stanie niespójnym. Przykładowo zmieniając setterami encję Adres możemy zmienić miasto ale nie zmieniać kodu pocztowego:P
Mimo tych wad, podejście tego typu jest często stosowane w "prostych" przypadkach.


Strzałka po prawej stronie diagramy prezentuje inne podejście. W warstwie aplikacji obiekty biznesowe są przepakowywane do/z obiektów transportowych DTO. Wiem, że przepakowywanie obiektów kojarzy się z robotą szympansa, ale dzięki temu możemy uniknąć powyższych wad i niezręczności. Zaraz zaraz... jak pobrać stan obiektu biznesowego gdy nie ma on getterów? Myśl obiektowo; nie pytaj, wydawaj rozkazy! Oto rozwiązanie.

Podejście opierające się na przepakowywaniu ma jednak wadę polegającą na tym, że zupełnie niepotrzebnie pobieramy z repozytorium nadmierną ilość danych - ponieważ dany obiekt transferowy (potrzebny w danym przypadku użycia np. dla danego ekranu) może potrzebować znacznie mniej danych niż zawiera np agregat.



O ile powyższe podejścia do warstw są zadowalające, to prawdziwy underground pojawia się gdy uznamy, że jeden wymiary (wysokość warstw) nie wystarcza i potrzebujemy drugiego - heh to stary trik fizyków. Gdy brakuje im wymiarów aby upchać swe teorie to zakładają istnienie nowych -stąd w różnych odmianach Teorii Strun 11 a nawet 26 wymiarów czasu i przestrzeni;P

Ortodoksyjna architektura DDD wymaga istnienia 2 stosów. Jeden do "prymitywnych" operacji CRUD, drugi natomiast do obsługi rozkazów dla modelu domenowego. Podejście to zapewnia nam utrzymanie eleganckiego stylu Command-Query Separation. Stylu w którym polecenia odczytu stanu i polecenia zmiany stanu są jasno i przejrzyście odseparowane. Posiadanie osobnych klas manipulacyjnych stanem pozwala łatwo audytować i kontrolować ten kod.

Zauważmy, że stos DDD jest tylko do zapisu. Dokonujemy w nim zmian wysyłając komunikaty z poleceniami (wzorzec command). Obiekty biznesowe mogą ew. wygenerować swoje komunikaty i umieścić je w pipeline. Natomiast klient może odpytać o stan jedynie poprzez stos CRUD wysyłając komunikat z kwerendą.

Dodatkowo stos CRUD może zwracać Encje (nie biznesowe a anemiczne - czyli "normalne") będące obiektami transferowymi "szytymi" na miarę.

Separacja przetwarzania logiki od magazynu danych pozwala tworzyć architektury przygotowane na masowe obciążenie i skalowanie - DDDD (4xD to nie pomyłka, ale Distributed DDD). Architektury, gdzie liczy się coś więcej niż procedury i encje. Architektury, w których 2 stosy odpowiadają za osobne moduły z osobnymi magazynami danych w szczególności. Magazynami, które mogą być optymalizowane względem czasu wykonania (stos Command) i czasu wykonywania przekrojowych raportów (stos Query). Magazyny, które mogą asynchronicznie się komunikować i synchronizować.

Ale to temat na osobnego posta... Zainteresowanym polecam wywiad z Gregiem Youngiem - jednym z guru DDD.

//=========================

Tak wyglądają lekkie modyfikacje standardowych architektur warstwowych dopasowane do DDD i DDDD. W świecie Javy jest to jeszcze underground...

Ostatnią z prezentacji, która zakończyła moją sesję na 4developers była "Understanding Distributed Messaging Systems and Where They Fit" Raymonda Lewallena
ze ścieżki .NET.

Raymond opowiadał o podstawowych architekturach dużych i skalowalnych systemów rozproszonych opartych o komunikaty. Jakież było moje zdziwienie gdy Raymond wspomniał nagle coś o Agregacie, później o tym, że wysyła on komunikat do pipleina aż wreszcie zobaczyłem diagram architektury 2-stosowej. Dla niego było to takie oczywiste i naturalne, że używa DDD... nawet o tym nie wspomniał i nie robił żadnego wprowadzenia... po prostu standard:)

Chyba chłopaki od .NET wiedzą w co się bawić;P

4 komentarze:

Maciej Zubala pisze...

Widzę, że robisz karierę w wielkim świecie - gratulacje. Fajny slajd z turbo pascalem ;)

A co do nadmiaru informacji w DTO, to nie zgodzę się, że wprowadzenie drugiego stosu warstw jest jedynym rozwiązaniem. Nie jest powiedziane przecież, że DTO mają w stosunku 1:1 odpowiadać obiektom biznesowym. Można je przecież tworzyć pod konkretne serwisy aplikacyjne tak aby zawierały tylko potrzebne pola w danej sytuacji. A jeśli już mamy DTO odpowiadające ściśle encjom, to zawsze możemy w nich przesyłać tylko potrzebne informacje nie ustawiając pół zbędnych do wykonania danego use casu.

Pierwszy raz widzę koncepcję tego alternatywnego stosu warstw - pewnie dlatego, że to underground ;) Wydaje mi się, że istnieje tutaj pewne ryzyko, że używając tego drugiego stosu omijając repozytoria możemy "namieszać" w bazie danych, tak że w stosie DDD nie będziemy mogli odtworzyć poprawnego stanu encji. No ale pewnie sam tego nie wymyśliłeś i jest w tym głębszy sens ;)

pozdro

Maciek

Sławek Sobótka pisze...

Źle się wyraziłem, nie chodziło mi o nadmiar danych w DTO. Miałem na mysli to co piszesz, czyli DTO "szyte na miarę" juzkejsu (ekranu). Natomiast problem z ilością danych polega na tym, że najpierw pobieramy agregat DDD z nadmiarowymi danymi (w stosunku do potrzeb np ekranu) a później tylko część z nich przepakowujemy do DTO.

Co do 2 sosów, to oczywiście, że sam bym tego nie wymyslił - zobacz wywiad z Gregiem Youngiem, który podlinkowałem.

Maciej Zubala pisze...

"Natomiast problem z ilością danych polega na tym, że najpierw pobieramy agregat DDD z nadmiarowymi danymi (w stosunku do potrzeb np ekranu) a później tylko część z nich przepakowujemy do DTO."

Taki problem może dotyczyć nie tylko zwykłego odczytu na potrzeby prezentacji, ale też logiki biznesowej, kiedy wykonanie jakiejś metody biznesowej nie wymaga operacji na całości grafu obiektów wchodzących w skład agregatu, lecz na pewnej jego, niewielkiej części. Jeśli używamy realcyjnej bazy z jakimś maperem, to możemy użyć lazy loadingu i problem z dyni. Nie wiem jak to jest w obiektowych bazach z ładowaniem danych tylko w razie potrzeby - nigdy ich nie używałem.

iirekm pisze...

Właśnie obejrzałem ten wywiad z Gregiem Youngiem i spodobał mi się tam tekst:
"Our domain ends up cleaner, our DTO layer ends up cleaner and let me just say I would hate to be the junior programmer who has to write all the DTO mapping code - It's a pain! Been there, done that."

Od takiej czarnej roboty jak przepakowywanie do DTO powinny być frameworki, AOP, generatory kodu itp. - coś co ogólnie się nazywa metaprogramowaniem, o czym pisałem jakiś czas na swoim blogu.

Do uzystkania tych dwóch stosów i command-query separation tak naprawdę wystarczyłby dobry framework, który by się zajmował przapakowywaniami, przesyłaniami, itp, i ukrywał te optymalizacyjno-enkapsulacyjne szczegóły przed programistami. Do programisty/designera ciągle należałoby jedynie zaprojektowanie encji, co na pewno poprawiłoby produktywność i łatwość rozbudowy.