Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

W trakcie pracy nad modelem domenowym natrafiłem na dość irytujący problem. Mianowicie, nie udaje mi się dla specyficznego biznesu poprawnie zamodelować agregatów.
Wyobraźmy sobie system to obsługi paczek pocztowych. Oprócz oczywistego składowania paczek, biznes wymaga, żeby utworzyć kategorię paczek. Kategoria paczki wyraża jakie cechy może mieć paczka, żeby należeć do danej kategorii. Każda kategoria narzuca swoje limity rozmiarów w trzech wymiarach. Algorytm jest prosty, bierzemy najdłuższy rozmiar paczki i każda kategoria ma swój maksymalny rozmiar, system zawsze powinien wybierać możliwie najmniejszą kategorię. Biznes chce, żeby system miał możliwość edycji kategorii, zmiany jej rozmiaru, lub po prostu wyrzucanie, czy dodawanie nowych.

Zaczynamy modelowanie, pierwsze i oczywiste to agregat "Paczka", zawiera ona wszystkie cechy paczki, co kluczowe rozmiary to też cecha paczki.
No to teraz wprowadźmy kategorie. Jako, że kategoria nie jest składnikiem Paczki i musi być niezależnie modyfikowana, to tworzymy nowy agregat "Kategoria", który zawiera jej parametry w tym jej maksymalny rozmiar.

Wszystko wygląda na pierwszy rzut oka super, schody się zaczynają, kiedy zaczynamy implementować te agregaty. Modelowanie DDD ma swoje twarde zasady, wśród nich są te trzy:
- mamy dążyć do logiki w obiektach biznesowych, unikać anemicznej logiki jak ognia
- wszystko co zawiera agregat, musi być jego własnością, jak kasujemy agregat wszystkie wewnętrzne obiekty też
- agregaty powinny być wewnętrznie spójne, tzw. powinny zawierać biznesowe integrity constraint

W powyższym przykładzie nie da się tych rzeczy pogodzić. Jeśli umieścimy Kategorię w Paczce, to złamiemy zasadę 2. Jeśli zrobimy je oddzielnie to jesteśmy zmuszeni wypruwać i wciskać dane do agregatów, a cały ten biznes implementować w serwisie. Jakby tego było mało agregat nie może sprawdzić swojej spójności biznesowej, bo nie wie jak przydzielić sobie kategorię.

Czy jest może jakieś narzędzie w DDD, które przeoczyłem ? Czy może na opak interpretuje zasady modelowania DDD ? Przykład jest dość złośliwy, ale nie wierzę, że najlepsze co się da zrobić to anemiczna logika serwisowa.
Paweł Grzegorz Kwiatkowski

Paweł Grzegorz Kwiatkowski Architekt
oprogramowania,
Ericsson

Temat: DDD - Problem z tworzeniem agregatów.

A po co jest ta "Kategoria paczki" ? Jakaś logika na tym bazuje? Wyszukiwanie paczek kategorii X, prezentacja paczek?

Czy kategoria paczki zmienia się w czasie? np. w momencie nadania paczka była mała, a w momencie doręczenia zrobiła się duża? :)

-- Edited:
I czy paczka może być związana z wieloma kategoriami? np. zielona mała paczka, lekka duża paczka. Ten post został edytowany przez Autora dnia 26.06.13 o godzinie 14:10
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Paweł Grzegorz K.:
A po co jest ta "Kategoria paczki" ? Jakaś logika na tym bazuje? Wyszukiwanie paczek kategorii X, prezentacja paczek?

Tak z pewnością taki system powinien móc wyszukiwać paczki po kategorii. Od tego też z pewnością będzie zależała cena dostarczenia.
Czy kategoria paczki zmienia się w czasie? np. w momencie nadania paczka była mała, a w momencie doręczenia zrobiła się duża? :)

Powiedzmy, że biznes chce móc modyfikować kategorie zawsze kiedy chce. Jeśli kategoria się zmieni w trakcie życia danej paczki (może to być paczka nadana, ale jeszcze nie dostarczona), to takie paczki niepasujące do najnowszych kategorii biznes chce mieć oznaczone. Przekładając to na kod, agregat Paczka powinien móc wykryć, czy kategoria, do której został przydzielony jest nadal poprawna. Nie da się tego osiągnąć bez wypruwania agregatów z danych i tworzenia anemicznej logiki, przynajmniej ja nie widzę jak można by to zrobić....

EDIT:
I czy paczka może być związana z wieloma kategoriami? np. zielona mała paczka, lekka duża paczka.

Nie może, tak jak napisałem system zawsze wybiera najmniejszą kategorię.Ten post został edytowany przez Autora dnia 26.06.13 o godzinie 14:16
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Detale są w głównej mierze nieistotne. Mówiąc innymi słowami, cały ten problem można podsumować tak:

Jak dynamicznie wprowadzić do agregatu reguły biznesowe zależące od logiki i stanu innych agregatów, nie łamiąc przy tym zasad modelowania.
Paweł Grzegorz Kwiatkowski

Paweł Grzegorz Kwiatkowski Architekt
oprogramowania,
Ericsson

Temat: DDD - Problem z tworzeniem agregatów.

"Najmniejsza" sugeruje, ze masz określony porządek na właściwościach, ale czy tak jest w rzeczywistości?
np. regula1 { kolor = niebieski}, reguła2 { waga = 100g }, reguła3 { dlugosc <= 50 cm}.

Niebieska paczka o wadze 100 pasuje do co najmniej 2 reguł. Stąd było moje pytanie.

Skoro kategoria może się zmieniać, to trzeba ją wyliczać, albo aktualizować przy dodawaniu/modyfikowaniu/usuwaniu kategorii.

Dla wyliczania widzę 2 opcje:

Opcja 1):

interface IKlasyfikator {
Kategoria wyliczKategorie(Properties p);
}

class Paczka {
Properties p;
Kategoria dajKategorie(IKlasyfikator k);
}


Opcja 2) Serwis wołany, gdy zajdzie potrzeba:

class Klasyfikator {
Kategoria dajKategorie(Paczka p);
}


Trochę nie rozumiem stwierdzenia o wypruwaniu agregatu, więc może powyższe jest tym o czym już myśłałeś :)Ten post został edytowany przez Autora dnia 26.06.13 o godzinie 14:41
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Paweł Grzegorz K.:
Niebieska paczka o wadze 100 pasuje do co najmniej 2 reguł. Stąd było moje pytanie.

Za dużo skomplikowania w detalach :] Na początku napisałem, że algorytm przydzielania paczek bazuje tylko na rozmiarach. Bierze największy z trzech rozmiarów paczki i patrzy do której możliwie najmniejszej kategorii załapie się paczka.

Np. kategorie:
mała - do 40 cm
średnia - do 60 cm
duża - do 80 cm

paczka 10cm x 20cm x 50cm, największy rozmiar to 50 cm, więc najmniejsza możliwa kategoria to paczka średnia
Opcja 1):

interface IKlasyfikator {
Kategoria wyliczKategorie(Properties p);
}

class Paczka {
Properties p;
Kategoria dajKategorie(IKlasyfikator k);
}

No, to może być spoko pomysł, możemy podawać do agregatu obiekt, który będzie kategoryzował. Powstaje pytanie gdzie trafi ta logika biznesowa ? Czy przypadkiem ten schemat, nie wyciąga danych agregatu tak samo jak byśmy zrobili to w zwykłym serwisie ?
Opcja 2) Serwis wołany, gdy zajdzie potrzeba:

class Klasyfikator {
Kategoria dajKategorie(Paczka p);
}


Trochę nie rozumiem stwierdzenia o wypruwaniu agregatu, więc może powyższe jest tym o czym już myśłałeś :)

Tutaj masz wypruwanie agregatu, logika będzie anemiczna bo serwis bierze agregat, wyciąga z niego dane i przydziela mu kategorię. NIE jest to poprawne DDD pod żadnym pozorem, bo logika domenowa wypływa z obiektu do serwisu.
Paweł Grzegorz Kwiatkowski

Paweł Grzegorz Kwiatkowski Architekt
oprogramowania,
Ericsson

Temat: DDD - Problem z tworzeniem agregatów.

Opcja 1 - Paczka przekazuje dane do klasyfikatora, a klasyfikator oczekuje properties, a nie obiektów z domeny, więc to Paczka ma grzebać po swoich elementach, chyba że ma te dane "pod ręką".

Opcja 2 - wydawało mi się, że anemiczność dotyczy projektowania klas (opisujących domenę) w taki sposób, że stanowią odpowiednik tabelek bazodanowych, a pozbawione są operacji, które realizowałyby jakąś złożoną logikę ( tzn. coś poza get/set). W DDD operacje, które nie wiadomo gdzie koncepcyjnie ulokować, lądują w serwisach.

Ciężko mi określić czy ta opisywana Paczka ma jeszcze jakieś operacje.

Mogę sobie wyobrazić, że z Paczką związany jest List Przewozowy, który jest aktualizowany po wywołaniu jakiejś paczkowej metody paczka->dodajMetalowaSztabke().

Kto biznesowo klasyfikuje te paczki? PaniWOkienkuNaPoczcie? Bo chyba nie Klient? :)
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Paweł Grzegorz K.:
Opcja 1 - Paczka przekazuje dane do klasyfikatora, a klasyfikator oczekuje properties, a nie obiektów z domeny, więc to Paczka ma grzebać po swoich elementach, chyba że ma te dane "pod ręką".

Moim zdaniem nie ma znaczenia, czy przekonwertujemy te dane na jakąś inną postać, czy nie. Schemat realizacji anemicznej logiki pozostaje ten sam, czyli wyciągamy z obiektu jakieś dane, wykonujemy operację i wpompowujemy wynik logiki z powrotem do obiektu.
Opcja 2 - wydawało mi się, że anemiczność dotyczy projektowania klas (opisujących domenę) w taki sposób, że stanowią odpowiednik tabelek bazodanowych, a pozbawione są operacji, które realizowałyby jakąś złożoną logikę ( tzn. coś poza get/set). W DDD operacje, które nie wiadomo gdzie koncepcyjnie ulokować, lądują w serwisach.

No to racja, pomimo tego powinno się unikać serwisów jak tylko się da. Serwis domenowy jest niezbędny, kiedy wykonujemy operacje będące poza odpowiedzialnością każdego z agregatów. W tym przypadku odpowiedzialności są moim zdaniem jasne... Paczka powinna wiedzieć do jakiej kategorii należy i dlaczego, to jest jej wewnętrzny stan. Obiekty w DDD same myślą, szkoda że w real life tak nie ma :)
Ciężko mi określić czy ta opisywana Paczka ma jeszcze jakieś operacje.

Mogę sobie wyobrazić, że z Paczką związany jest List Przewozowy, który jest aktualizowany po wywołaniu jakiejś paczkowej metody paczka->dodajMetalowaSztabke().

Kto biznesowo klasyfikuje te paczki? PaniWOkienkuNaPoczcie? Bo chyba nie Klient? :)

Myślę, że znowu niepotrzebnie odlatujemy w dalsze szczegóły domeny. Ten przykład jest całkowicie wirtualny i miał na celu wyizolowanie szczególnego problemu na linii dwóch przykładowych agregatów.
A paczka może zawierać bardzo dużo innej logiki, np może mieć wiele stanów i związanych z tym zachowań. Np. paczkę można by anulować kiedy jest w statusie "przyjęta", ale już nie w statusie "dostarczana". Od tego jest system, żeby pani w okienku nie musiała się takimi rzeczami zajmować :)
Paweł Grzegorz Kwiatkowski

Paweł Grzegorz Kwiatkowski Architekt
oprogramowania,
Ericsson

Temat: DDD - Problem z tworzeniem agregatów.

Marcin M.:
Kto biznesowo klasyfikuje te paczki? PaniWOkienkuNaPoczcie? Bo chyba nie Klient? :)

Myślę, że znowu niepotrzebnie odlatujemy w dalsze szczegóły domeny. Ten przykład jest całkowicie wirtualny i miał na celu wyizolowanie szczególnego problemu na linii dwóch przykładowych agregatów.

No ale w DDD chyba o to chodzi, żeby skupiać się na domenie? :-) Czy ten kto określa kategorię paczki nie jest dobrym kandydatem na właściciela operacji DatKategorie() ? Zapewne wie skąd wziąć reguły i wie jaka jest konkretna Paczka i czy tę kategorię ma przypisać paczce czy może jakiemuś listowi przewozowemu.

Dodatkowa opcja, to może modelowanie związku Kategorii i Paczki jako klasy asocjacyjnej?
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Paweł Grzegorz K.:
No ale w DDD chyba o to chodzi, żeby skupiać się na domenie? :-) Czy ten kto określa kategorię paczki nie jest dobrym kandydatem na właściciela operacji DatKategorie() ? Zapewne wie skąd wziąć reguły i wie jaka jest konkretna Paczka i czy tę kategorię ma przypisać paczce czy może jakiemuś listowi przewozowemu.

No można by oczywiście przemodelować te agregaty, tylko stworzenie takiego, który zawiera i dane paczki i zasady przydzielania kategorii jest moim zdaniem niemożliwe. Bez względu na to, między jakie obiekty podzielmy ten biznes, zawsze kategorie muszą być niezależnie dodawane/usuwane względem paczek. Taka sytuacja wskazuje jednoznacznie, że te dane należą do innych agregatów.
W DDD nie ma koncepcji obiektu domenowego, który zajmuje się modyfikacjami innych obiektów domenowych. W modelu nie modelujemy "pracowników", którzy w realnym biznesie zajmują się przeróbką obiektów. Dobry agregat to hermetyczny, autonomiczny i świadomy swoich odpowiedzialności i zachowania obiekt biznesowy, który nie potrzebuje kolejnego obiektu "sprawcy", wykonującego na nim operacje.
Dodatkowa opcja, to może modelowanie związku Kategorii i Paczki jako klasy asocjacyjnej?

Już naturalnie jest między nimi asocjacja. Obiekt paczka ma w końcu przydzieloną jakąś kategorię, czyli asocjacja istnieje. Moim zdaniem jest to jednostronne jeden do wielu, Paczka trzyma odniesienie do swojej Kategorii.
Paweł Grzegorz Kwiatkowski

Paweł Grzegorz Kwiatkowski Architekt
oprogramowania,
Ericsson

Temat: DDD - Problem z tworzeniem agregatów.

Marcin M.:

Już naturalnie jest między nimi asocjacja. Obiekt paczka ma w końcu przydzieloną jakąś kategorię, czyli asocjacja istnieje. Moim zdaniem jest to jednostronne jeden do wielu, Paczka trzyma odniesienie do swojej Kategorii.

Przez "klasę asocjacyjną" miałem na myśli wyrażenie asocjacji klasą, czyli mamy 3 klasy: Paczka, Kategoria,
PaczkaKategoria. W ten sposób Paczka nie trzyma odniesienia do Kategorii i vice versa.

PaczkaKategoria byłaby utrwalana i miała jakąś logikę. Więc nie byłby to tylko serwis ;-)
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Paweł Grzegorz K.:
PaczkaKategoria byłaby utrwalana i miała jakąś logikę. Więc nie byłby to tylko serwis ;-)

Mógłbyś bardziej sprecyzować, jakie dane miałby taki obiekt i za jaką logikę by odpowiadał ?

Pewna inspiracja znajduje się tutaj: http://bottega.com.pl/pdf/materialy/ddd/ddd1.pdf

Jest tam zastosowany rzadko używany wzorzec Strategy Design Pattern. Czyli tworzymy obiekt Policy, który odpowiada za wariację zasady biznesowej. Wstrzykujemy coś takiego do wnętrza agregatu, dzięki temu logika w agregacie jest bardziej dynamiczna. Wyciąga to trochę logikę z agregatu i wiem, że to nie jest wcale idealne, ale chyba w tym wypadku nie ma innego wyjścia. Zastanawiam się teraz jak stworzyć te Policy, tak żeby możliwie najmniej logiki z tego agregatu wyciąć.
Paweł Grzegorz Kwiatkowski

Paweł Grzegorz Kwiatkowski Architekt
oprogramowania,
Ericsson

Temat: DDD - Problem z tworzeniem agregatów.

Marcin M.:
Paweł Grzegorz K.:
PaczkaKategoria byłaby utrwalana i miała jakąś logikę. Więc nie byłby to tylko serwis ;-)

Mógłbyś bardziej sprecyzować, jakie dane miałby taki obiekt i za jaką logikę by odpowiadał ?

np.


class PaczkaKategoria {
// wyraża asocjację między paczką, a kategorią
Paczka paczka;
Kategoria kategoria;

// ktoś musi wiedzieć jak sklasyfikować paczkę, alternatywnie mieć dostęp do repozytorium regul pozwalajacych określić właściwą kategorię
IKlasyfikator klasyfikator;

// uprzednio wyliczona, bądź taka, która była w składzie danych
Kategoria dajKategorie();

// z wykorzystaniem klasyfikatora albo ta klasa wie jak sięgnąć do repozytorium reguł i jak je przetworzyć
Kategoria wyliczKategorie();

// porównuje bieżącą kategorię z kat. wyliczoną w oparciu o reguły z repo. lub wyliczoną przez klasyfikator
bool kategoriaUleglaZmianie();

}


W ten sposób Paczki/Kategorie utrzymywane są osobno, a informacje o powiązaniach osobno.
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Rozwiązanie przyszło samo, wystarczyło dokładnie przeanalizować odpowiedzialności....
- odpowiedzialnością paczki, jest ustalenie kategorii na podstawie tego, do której pasuje(wybrać najmniejszą pasującą kategorię)
- kategoria powinna rozstrzygnąć czy konkretne dane paczki kwalifikują ją do kategorii, czyli efektywnie kategoria implementuje algorytm przydzielania(bierze największy bok i patrzy, czy mieści się w granicy)

W ten sposób mamy:


public class Paczka{
przydzielKategorie(Collection<Kategoria> kategorie){
// pętla po kategoriach, która wybiera najmniejszą pasującą
}
}


public class Kategoria{
czyDaneSieMieszcza(DanePaczki dane){
// tutaj algorytm sprawdzajacy dane paczki
}
}


Te rozwiązanie jest nie tylko zabójczo proste, ale też bardzo elastyczne. Dzięki temu możemy tworzyć różne implementacje kategorii, nie zmieniając agregatu Paczka. Jedyny problem polega na konieczności podawania do Paczki kategorii, za każdym razem, kiedy chcemy wiedzieć do jakiej kategorii Paczka należy.
Łukasz Kwiatkowski

Łukasz Kwiatkowski Programista Java

Temat: DDD - Problem z tworzeniem agregatów.

Kategoria paczki powinna się uaktualniać (być wyznaczana) przy ustawianiu pola "danePaczki". Setter dla pola "kategoria" powinien być prywatny.
Takie podejście umożliwi Ci korzystanie z różnych strategii wyznaczania kategorii paczki jak również uniemożliwi ręczne ustawianie kategorii.


public interface WyznaczKategorieStrategia {
Kategoria wyznacz(DanePaczki danePaczki);
}

public class Paczka {

private Kategoria kategoria;
private DanePaczki danePaczki;
private WyznaczKategorieStrategia wyznaczKategorie;

public void setDanePaczki(DanePaczki danePaczki) {
this.danePaczki = danePaczki;
uaktualnijKategorie();
}

private void uaktualnijKategorie() {
Kategoria wyliczonaKategoria = wyznaczKategorie.wyznacz(this.getDanePaczki());
setKategoria(wyliczonaKategoria);
}

public DanePaczki getDanePaczki() {
return danePaczki;
}

private void setKategoria(Kategoria kategoria) {
this.kategoria = kategoria;
}
}
Marcin Mroczkowski

Marcin Mroczkowski Programista JAVA/JEE

Temat: DDD - Problem z tworzeniem agregatów.

Łukasz K.:
Kategoria paczki powinna się uaktualniać (być wyznaczana) przy ustawianiu pola "danePaczki". Setter dla pola "kategoria" powinien być prywatny.

Posiadanie public settera na dane paczki to trochę zaprzeczenie idei agregatów, które muszą być hermetyczne. Taka logika ma rację bytu w fabryce, ale z pewnością nie w agregacie.
Takie podejście umożliwi Ci korzystanie z różnych strategii wyznaczania kategorii paczki jak również uniemożliwi ręczne ustawianie kategorii.

Nie wiem, co rozumiesz pod pojęciem "ręczne ustawianie", ale podejrzewam że chodzi Ci o metodę "przydzielKategorie" w moim kodzie. Jak widzisz nie wyspecyfikowałem poziomu dostępu tej metody, chodziło mi tylko o pokazanie, jak paczka może sama sobie tą kategorię wyznaczyć. Równie dobrze może być to prywatna metoda działająca w ramach jakiejś większej akcji biznesowej.
Odnośnie samej strategii, owszem całkiem nieźle to wygląda na pierwszy rzut oka, jednak moim zdaniem w tym przypadku mijamy się z przeznaczeniem tego patternu. Strategię stosuje się, kiedy zauważymy kruchy kawałek logiki biznesowej, który może zmienić się w zależności od zewnętrznych czynników. Tutaj logika wyznaczania kategorii jest stabilna - system na wybrać najmniejszą kategorię, zawsze i jest to niezmienne. Dynamiczne w tym przypadku są kategorie, nie logika paczki. Przez tworzenie strategii wyciągniemy tą logikę z agregatu, zrobimy dokładnie odwrotnie, niż to co zaleca DDD, czyli zamiast uwypuklić biznesową logikę, ukryjemy ją za interfejsem.
Zauważ, że w przypadku mojego rozwiązania kategorie są nadal całkowicie dynamiczne. Paczka dostaje ich listę, ale w gruncie rzeczy nie wie jakimi obiektami są. Możemy skorzystać z dobroci polimorfizmu i tworzyć różne typy kategorii, które mogą implementować dowolne algorytmy przydziału.

Następna dyskusja:

Przykład DDD




Wyślij zaproszenie do