niedziela, 10 sierpnia 2008

Z pamiętnika zdomenowanego modelarza: #3 Artefakty biznesowe

Po krótkim wstępie do Domain Driven Design i po zdawkowym opisie architektury oraz otrząsnąwszy się z ulgą z dysonansu czas przyjrzeć się artefaktom z warstwy domenowej.

W kontekście trzech ostatnich dyskusji (głównie pozablogowych) spróbuję zsyntetyzować zagadnienia podstaw OO (kontrowersyjne dziedziczenie, wzorce projektowe) z paradygmatami DDD.

W DDD logikę domenową modelujemy za pomocą kilku rodzajów artefaktów (które Evans w swej książce nazywa Building Blocks). Mamy zatem niejako do wyboru kilka podstawowych figur, którymi możemy zagrać na różne sposoby:

Encje - rozróżnialne (w przeciwieństwie do Value Objects) obiekty. Intuicyjnie jest to coś co mapujemy na tabele (nie koniecznie 1-1 i nie koniecznie wszystkie atrybuty). W DDD ważne jest aby encje miały oprócz atrybutów również odpowiedzialność (metody biznesowe) - w przeciwnym wypadku mamy do czynienia z anemicznym modelem, o którym już pisałem.

Value Objects (VO) - po prostu opisują jakiś rzeczy i nie są rozróżnialne (w przeciwieństwie do encji). Zazwyczaj nie powinny być modyfikowalne - można je współdzielić więc modyfikacja w jednym miejscu popsuła by szyki w innym. O ile encje zazwyczaj odczytujemy z bazy (lub tworzymy po to aby je tam zapisać) to VO zwykle powstają w wyniku działania Servisu.

Servisy - czasem po prostu pewnych odpowiedzialności nie możemy w naturalny przypisać do żadnej encji ani VO. Wówczas potrzebujemy servisu, który się tym zajmie. Zwróćmy uwagę na podejście - Servis jest na równym poziomie (warstwie) z Encją! Nie jest to servis aplikacyjny (leżący ponad warstwą domenową) a servis biznesowy - artefakt tej samej "wagi" co encja.

Agregaty - rzeczywistość nigdy nie jest na tyle prosta by zamodelować ją przy pomocy jednej Encji. Agregaty to nic innego jak drzewa encji i VO. Ideą agregatu jest to, że mamy dostęp do "roota", który jest niejako interfejsem - to zawsze jemu wydajemy rozkazy - natomiast w jego gestii leży ewentualne delegowanie odpowiedzialności do swych składników.
Łańcuszki typu getCzłowiek().getUkładTrawieia.getŻołądek().add(new Kiełbasa(3)) są niedopuszczalne ponieważ łamią paradygmat enkapsulacji (który o zgrozo jest mylony z prywatnymi polami i publicznymi getterami/setterami). Oczywiście w tym przykładzie człowiek powinien mieć jakiś interfejs (w sensie metodę a nie w sensie słówka kluczowego interface) do karmienia go.
Agregaty są jako spójna całość zapisywane i odczytywane. Z agregatem łączy się kolejny artefakt (już raczej techniczny niż projektowy) fabryka - hermetyzująca po prostu złożony proces tworzenia nowego agregatu (tworzenia nowego a nie odczytywanie istniejącego ze źródła danych).

Polityki - last but not least. Po prostu stary dobry u sprawdzony wzorzec strategii. Gdy jakiś proces biznesowy (lub jego etap) jest alternatywny, lub spodziewamy się jego zmiany (jest niestabilny lub nie do końca przeanalizowany) wówczas enkapsulujemy go poza stabilny interfejs. Znowu enkapsulacja trochę bardziej wyrafinowana niż prywatne pole i publiczny getter/setter. Przy okazji (już o tym pisałem, ale nigdy za wiele powtarzać) trzymamy się GRASP.


Podsumowując w kontekście dziedziczenia: Jak widać DDD zachęca raczej do agregacji. Na dziedziczenie możemy sobie pozwolić np w politykach. Same polityki agregujemy we wnętrzu Servisów, Encji czy VO, ale na niższym poziomie - poziomie implementacji możemy śmiało i bez obaw zagrać dziedziczeniem. Polityka jest koherentna, bo z definicji posiada ściśle określoną jedną odpowiedzialność więc nie będzie tu raczej problemów opisywanych ostatnio.

Zatem patrząc ogólniej: dziedziczenie jest przeniesione z poziomu Encji, VO i Servisów na poziom składników - polityk. W wyniku zamiast definiować kilka różnych klas np Klientów (zwykły, vip, lojalny itp) możemy zaprojektować jedną klasę klienta składającą się między innymi z PolitykiRabatowej, która jest interfejsem. A jak to się w przyszłości rozwinie - nie jest to ważne z punktu widzenia klasy Klienta. Po kolejny przykład zapraszam tu.

//=================================
Porównajmy to z klasycznym podejściem. Znajdujemy rzeczowniki, projektujemy bazę, generujemy z niej klasy encji (właściwie to pascalowe rekordy, albo stuct z C). Później już tylko potrzeba paczek procedur (czasowniki) zamkniętych w klasy (po co te klasy? tylko przeszkadzają - nakładanie gaci przez głowę) i gotowe. Aby bylo bardziej biznesowo to rekordy umieszczam w pakiecie modelu albo domain a paczki procedur w pakiecie services. Radośnie programuję w Pascalu w Javie:)

1 komentarz:

marekd pisze...

Twoje ostatnie słowa dokładnie odpowiadają temu co widzę w projekcie w którym teraz uczestniczę ... Anemiczny model :-) Na szczęście za miesiąc się dowiem czy on wogóle ma szansę sie powieść czy nie, najwyżej poszukam innej pracy i wtedy będę bardziej wybredny ;-)