> Już szykuje się ekipa ukraińców z Michałem Margielem na czele > czychająca na głowę Jacka Laskowskiego za nienagrywanie dzisiaj :)
A żebyś wiedział! oberwie mu się za to!
Co do prezentacji no to sorry Paweł, że to napiszę ale była (za programem Wujek Dobra Rada)...
To wspaniała prezentacja Pawle, Świetna prezentacja, bardzo dobra prezentacja, Pycha!
Całkiem serio uważam, że zarówno merytorycznie jak i "szołmeńsko" było kapitalnie. Pokusiłbym się nawet, że była to jedna z najlepszych prezentacji w historii naszego juga.
Chyba z dobrą prezentacją jest tak jak z dobrym filmem - po obejrzeniu skłania nas on do przemyślenia pewnych spraw. I tak właśnie było w przypadku dzisiejszej prezentacji, i może w następnym projekcie uda nam się zastosować zasady przedstawione przez Pawła.
Dlatego tym bardziej jestem zawiedziony, że kamera się nie pojawiła.
Ps. w ramach statystyk to ja się naliczyłem 69 osób na prezentacji + 1 prowadzący, co stawia prezentacje o DDD w czołówce najchętniej wysłuchanych .
Jeszcze raz dzięki serdeczne i mam nadziej, że nie będziemy musieli długo czekać na Twój następny show :) -- Pozdrawiam/Best regards Michał Margiel
Dzięki za prezentacje, do tej pory nie bardzo rozumiałem czym do końca jest DDD i jak zwykle okazało się to proste i zrozumiałe (człowiek zawsze boi się nieznanego ;-)
Sama idea modelu dziedziny, który jest czymś więcej niż zbiorem klas mapowanych z bazy danych jest dla mnie jasna, jednak już cała otoczka nie do końca, szczególnie pojęcie Serwisów. Jednak postaram się pogłębić swoją wiedzę w tym temacie.
Trochę zabrakło mi "mięsa", jakiegoś prostego przykładu, takiego Hello World DDD! Cóż będę musiał obejść się smakiem ;-) A może dasz się namówić na powtórzenie prezentacji w ramach Warszawa DP?
Paweł Lipiński pisze: > Wszystkich, którym nie zdążyłem odpowiedzieć po prezentacji > przepraszam i zapraszam do dyskusji tu na forum.
Lukasz Lenart pisze:
> Trochę zabrakło mi "mięsa", jakiegoś prostego przykładu, takiego Hello > World DDD!
Ja też się dołączam do marudzenia :)
Czytałem wcześniej artykuły na InfoQ, słuchałem mądrych ludzi na wykładach i wszystko wygląda pięknie, ale chciałbym zobaczyć na własne oczy działający kod.
Dasz się namówić na przykład (do ściągnięcia) prostej, webowej aplikacji DDD opartej na Spring 2.5/3.0 + Hibernate? Takiej, która pokazuje:
1. jak wyszukać obiekty z poziomu serwisu skoro nie ma w nim dao/repozytorium (dostęp do repozytorium tylko z warstwy domenowej)
2. do czego mają służyć fabryki o których wspomniałeś na wykładzie (na poziomie domeny), skoro hibernate zapewni nam lazy loading
3. do czego mają nam służyć agregatory o których wspomniałeś na wykładzie, skoro hibernate/jpa załatwia za nas agregację obiektów domenowych w obiektach domenowych?
I żeby to rozwiązanie wynikało z Twoich doświadczeń z DDD (w sensie best practices, a nie "byle-by-było-development").
Poza tym wykład bardzo przyjemny. Masz dar podobny do Jacka Laskowskiego, który potrafi obudzić ludzi na ostatnich godzinach całodniowej konferencji, kiedy kawa dawno przestała już działać ;)
W dniu 17 marca 2009 23:58 użytkownik Jakub Nabrdalik <jak...@gmail.com> napisał:
> Czytałem wcześniej artykuły na InfoQ, słuchałem mądrych ludzi na > wykładach i wszystko wygląda pięknie, ale chciałbym zobaczyć na własne > oczy działający kod.
Taki przykład jest dostępny w sieci: http://dddsample.sourceforge.net/. Przy tworzeniu tego projektu brał udział sam Eric Evans. Jest to kompletna aplikacja webowa w duchu DDD, która ma dokładnie taką architekturę, jaką przedstawiał wczoraj Paweł. Zastosowano w niej popularne obecnie narzędzia m.in. Spring, Hibernate, ActiveMQ, co świadczy o tym, że DDD wcale się z nimi nie "gryzie". Polecam obejrzenie tego kodu. Dla mnie był to pouczający przykład.
A mnie sie podobalo szołmeńsko (choć czekałem tylko jak prowadzący ogłosi, że zmienia imie na Evans ;-) ) i merytorycznie ale...
Chcialem zadac pytanie i mnie zakrzyczales i olales :-p
Zadam tutaj:
Bardzo niepodobal mi sie antyprzyklad z warstwami. IMO to bylo dokladnie to samo, tyle ze zamiast Fasada bylo Serwis + Domena. No nieslychanie duza zamiana ktora wywraca do gory nogami podejscie do programowania ;-). Jak rozumiem problemem dla ciebie bylo w tamtym przykladzie nawalanie klodem po kontrolerze, ale wcale nikt nie mowil, ze sie tak powinno robic.
I tu zgadzam sie poniekad z kolega z tylu. W prezentacji brakowalo mi konsekwencji. Antyprzykladem bylo programowanie neizgodne z jakimikolwiek zasadami (jest jakis tam pattern, ale my nawet niego sie nie trzymamy) a DDD pokazywalo jak to sie powinno robic dobrze - i tutaj juz niby kazdy usiadzie i zadnego kodu nie napisze w kontrolerze, w domenie nie wymsknie mu sie JDBC itp. itd.
Zreszta jak sie otworzy same obrazki to nie ma w nich nic zdroznego - wymaga ten z lewej komentarza :].
No ale tak czy inaczej prezentacja fajna i generalnie DDD ladnie zbiera pewne dorbe praktyki w calosc. Jak powiedzial mi Jacek K. po prezentacji jest to taka rzecz, ktora jak sie czyta to czlowiek mysli "no przeciez wiadomo, ze tak powinno byc", ale jak nie przeczyta, to bedzie robil zle ;)
W dniu 18 marca 2009 08:29 użytkownik Tomek Szymański <szim...@szimano.org> napisał:
> Bardzo niepodobal mi sie antyprzyklad z warstwami. IMO to bylo > dokladnie to samo, tyle ze zamiast Fasada bylo Serwis + Domena. No > nieslychanie duza zamiana ktora wywraca do gory nogami podejscie do > programowania ;-). Jak rozumiem problemem dla ciebie bylo w tamtym > przykladzie nawalanie klodem po kontrolerze, ale wcale nikt nie mowil, > ze sie tak powinno robic.
Nie - problem polega na tym, że cała logika biznesowa jest w metodach Fasady, co nie ma nic wspólnego z programowaniem obiektowym i do złudzenia przypomina programowanie proceduralne w Pascalu. W tym właściwym podejściu chodzi o to, że logika biznesowa jest realizowana w warstwie domenowej przez obiekty, które łączą w sobie dane i zachowania. Serwisy natomiast mają za zadanie jedynie pobrać obiekty domenowe z repozytorium i zlecić im wykonanie logiki biznesowej. Więcej o antywzorcu, który opisał Paweł możesz przeczytać choćby w wikipedii ;) http://en.wikipedia.org/wiki/Anemic_Domain_Model albo u Martina Fowlera, który jako pierwszy go opisał: http://martinfowler.com/bliki/AnemicDomainModel.html.
> Sama idea modelu dziedziny, który jest czymś więcej niż zbiorem klas > mapowanych z bazy danych jest dla mnie jasna, jednak już cała otoczka > nie do końca, szczególnie pojęcie Serwisów. Jednak postaram się > pogłębić swoją wiedzę w tym temacie.
No więc Łukaszu wyobraź sobie taką sytuację: robisz aplikację typu CMS, są tam autorzy, artukuły (tu tytuł, data, treść, etc), działy itp. I to wszystko ładnie możesz zamodelować dziedziną. A tu przychodzi do Ciebie prośba o dorobienie RSS do tego. W tym RSSie ma być nazwisko autora i tytuł artykułu. No to możesz jechać po dziedzinie, ale to pewnie nie będzie zbyt wydajne, albo walnąć serwis, który zwraca ten RSS pukając bezpośrednio przez repozytorium do bazy. Pseudokod (więc nie czepiać się szczegółów): public class ArticleRssService { public List retrieveLastArticlesFromSection (Section section) { List articles = articleRepository.findLastArticlesFromSection(section); //jakieś przetworzenie listy do wersji wygodnej do wygenerowania RSSa return resultingList; }
}
public class ArticleRepository extends Repository { public List findLastArticlesFromSection (Section section) { return session .createQuery("select a.title, a.author.name from Article a where a.section=:section order by a.date desc") .setParameter("section", section) .setMaxResults(25) .list(); }
}
Mógłbyś jednak woleć z jakiegoś powodu iść bezpośredio do dziedziny i wtedy serwis będzie opakowywał wywołania jakichś metod dziedziny (definiował jakiś algorytm dla nich) - np.: public class ArticleRssService { public List retrieveLastArticlesFromSection (Section section) { List articles = articleRepository.findLastArticlesFromSection(section); return createListOfRssEntries(articles); }
private List createListOfRssEntries (List articles) { List resultingList = new ArrayList(articles.size()); for (Article article : articles) resultingList.add(article.returnAsRssEntry()); return resultingList; }
}
Inny przykład dla serwisów to np.: redaktor w tym CMSie chce dodać artykuł i opublikować go o 9:00 następnego dnia - ma do tego 1 klawisz gdzieś w UI. Więc masz serwis: public class ArticlePublishingService { public void saveAndPublish(String articleName, String articleText, String authorName, Date publishingDate) { Article article = createArticleAggregate(articleName, articleText, authorName); articleRepository.save(article); publisher.publish(article, publishingDate); }
Z resztą ten publisher to pewnie też jakiś serwis, który jest wołany np. z Quartza co minutę i publikuje wszystkie artykuły do opublikowania w tym czasie. To typowy przykład funkcjonalności nie za bardzo odpowiadającej dziedzinie.
Co do HelloWorld, to może coś napiszę jak znajdę czas w weekend, tym bardziej, że jak widzę jest większe zapotrzebowanie na to :) Co do DP meetingu, to też pewnie można by się dogadać.
--------------------- Jakub Nabrdalik: 1) Nie tylko nie mówiłem, że z serwisów nie powinno być dostępu do repozytorium, ale nawet explicite powiedziałem, że jak najbardziej powinien być (patrz mój przykład z CMSem dla Łukasza powyżej). Diagram pokazywał warstwy a nie wszystkie możliwe zależności, i chyba po prostu był mylący. Dostęp do repozytorium z serwisów jest zwykle niezbędny (skąś w końcu trzeba te obiekty dziedziny na których pracujemy uzyskać: albo z repozytorium jak już tam są, albo z fabryki jeśli tworzymy nowe).
2) Fabryki są do tworzenia nowych obiektów dziedziny i powinny być używane wyłącznie jeśli jest taka potrzeba (tzn konstrukcja tych obiektów jest złożona - powiedzmy heurystycznie, że więcej niż 2 linijki). Głównie dotyczy to więc agregatów. Tak jak mówiłem, fabryki powstają zwykle jako skutek refaktoryzacji 'extract method' gdy tworzenie nowych obiektów/agregatów jest złożone.
3) Agregaty mają nam ułatwić zarządzanie grafami encji tak, by nie było niezbędne za każdym razem zarządzanie każdą encją oddzielnie. DDD nie jest związane z hibernatem, więc nie zakłada istnienia takich mechanizmów. Ale skoro już je mamy, to część sprawy jest załatwiona (przynajmniej relacje i cascading). Tak czy siak można sobie wyobrazić sytuację, w której różne zestawy encji o wspólnym ich podzbiorze muszą być zarządzane wspólnie (tzn każdy taki agregat oddzielnie) i statyczne deklarowanie sposobu ładowania i relacji w hibernacie może nie wystarczać. Np. w jednym widoku w aplikacji potrzebujemy danych autor 1-* artykuł *-* sekcja a w innym autor *-1 miasto 1-filia_firmy Wspólny jest tu tylko autor. Nie chcemy ładować artykułów autora przy wyszukiwaniu wszystkich autorów z jakiegoś miasta, ani czytelnika artykułu nie interesuje (a więc do jego widoku nie powinno być ładowane) w jakiej filii naszej firmy dany autor jest zatrudniony. Stąd różne agregaty mimo wspólnego podzbioru encji.
--------------------- Tomek Szymański: Do odpowiedzi Maćka dodam jeszcze jedno: rzuć okiem na kod powyżej (ten dla Łukasza) i porównaj go z bardziej popularną/klasyczną wersją (tak, jestem tu trochę tendencyjny):
@Session public class ArticlePublishingBean { public void saveAndPublish(String articleName, String articleText, String authorName, Date publishingDate) { Article ar = new Article(articleName, articleText); Author au = em.createQuery("from Author a where a.name=:name).setParameter("name",authorName).uniqueResult(); ar.setAuthor(au); em.persist(ar); SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler sched = schedFact.getScheduler(); sched.start(); JobDetail jobDetail = new JobDetail("publisher", null, ArticlePublisher.class); Trigger trigger = TriggerUtils.makeMinutelyTrigger("publishTrigger"); sched.scheduleJob(jobDetail, trigger); }
}
Kod powyżej jest bardzo typowy dla większości aplikacji - robi wiele rzeczy w jednej metodzie (w końcu to fasada), nie ma rozróżnienia poziomu abstrakcji operacji. Jest po prostu płaski - typowo proceduralny i do tego nieporozbijany na drobniejsze metody. Sam refactoring w postaci wydzielenia schedulowania, tworzenia encji i zapisu do oddzielnych metod wiele nie poprawi, bo dalej klasa będzie miała wiele różnych odpowiedzialności (patrz wpis na moim blogu na temat Single Responsibility Principle dot. klas). Jak pododajesz jeszcze 5-6 takich funkcjonalności, klasa będzie miała 25 różnych metod a po roku przestanie być utrzymywalna. W przypadku podziału warstw odpowiedzialności są rozłożone warstwami (odpowiednio: serwisy, dziedzina, repozytorium) i ten problem znika.
------------------
A tak w ogóle to dziękuję za miłe przyjęcie i recenzje :)
> Tomek Szymański: > Do odpowiedzi Maćka dodam jeszcze jedno: rzuć okiem na kod powyżej > (ten dla Łukasza) i porównaj go z bardziej popularną/klasyczną wersją > (tak, jestem tu trochę tendencyjny):
Ale mnie chodzi tylko o to, ze robienie plaskich fasad jest Złe. Nikt tak komu nie kazał pisać, ani nikt tak nikogo nie uczył (chyba, że ja byłem w innych szkołach :-p ). A przedstawiałeś to tak jakby do 17 Marca 2009 17:59 wszystkie autorytety na niebie i ziemi (poza Evansem of cors) uczyly, ze nalezy robic plaskie fasady zaraz za kontrolerem, ktore nawalaja po DAO.
W dniu 18 marca 2009 10:07 użytkownik Paweł Lipiński <lipinski.pa...@gmail.com> napisał:
> Luuudzie, ale się rozpisaliście!
To nie trzeba było rozbudzać apetytów ;-)
> [ciach]
Jednak się przyczepię, czy nie lepiej jednak by było upchnąć taką metodę w dziedzinie (Section.getArticlesAsRSS() ?), która wywołuje metody ArticleRssService? Chodzi mi o ten konkretny przypadek, wtedy UI, klient, etc. nie ma pojęcia o serwisie.
> Inny przykład dla serwisów to np.: redaktor w tym CMSie chce dodać > [ciach]
To bardziej do mnie przemawia i w sumie racja, nie jest to element dziedziny i powinno być robione obok. Tak czułem, że oto chodzi i tylko potrzebowałem potwierdzenia ;-)
> Co do HelloWorld, to może coś napiszę jak znajdę czas w weekend, tym > bardziej, że jak widzę jest większe zapotrzebowanie na to :)
Ten przykład, który ktoś podał DDSample jak dla mnie będzie ok, więc nie musisz się męczyć.
> Co do DP meetingu, to też pewnie można by się dogadać.
Oczywiście, preferujemy dyskusję i kodowanie nad prezentację, czy też pokazanie jak to się robi, tak jak było ostatnio z SOLID - rewelacja. Teraz przymierzamy się doi czegoś w stylo Randori Session czy Coding Dojo.
Tomek, ja nie mówiłem o żadnych autorytetach tylko o rzeczywistości.
Tak wygląda większość aplikacji niezależnie czy tego chcesz czy nie,
czy tak jest dobrze czy nie i czy tak mówią autorytety czy nie. Ten
wykład to nie mój doktorat ani pana Evansa, który miałby zawierać
pomysły rewolucjonizujące tworzenie oprogramowania, tylko mały wkład w
podniesienie średniej jakości oprogramowania na naszej planecie.
Ja wiem, że na JUGu to może jest oczywiste, bo tam przychodzą ludzie
zainteresowani rozwijaniem swoich umiejętności, wiedzy itp. Ale
rzeczywistość większości aplikacji jest taka jaka jest i tylko przez
szerzenie (może nawet oczywistych, ale jakoś nieczęsto praktykowanych)
technik i idei można coś tu poprawić.
>> Tomek Szymański:
>> Do odpowiedzi Maćka dodam jeszcze jedno: rzuć okiem na kod powyżej
>> (ten dla Łukasza) i porównaj go z bardziej popularną/klasyczną wersją
>> (tak, jestem tu trochę tendencyjny):
> Ale mnie chodzi tylko o to, ze robienie plaskich fasad jest Złe. Nikt
> tak komu nie kazał pisać, ani nikt tak nikogo nie uczył (chyba, że ja
> byłem w innych szkołach :-p ). A przedstawiałeś to tak jakby do 17
> Marca 2009 17:59 wszystkie autorytety na niebie i ziemi (poza Evansem
> of cors) uczyly, ze nalezy robic plaskie fasady zaraz za kontrolerem,
> ktore nawalaja po DAO.
na wstępie - prezentacja bardzo dobra :). Ale faktycznie przydałby się
chyba jakiś ciąg dalszy z przykładami :)
Co do samego DDD, to nie za bardzo rozumiem jaka logika miałaby się
znaleźć w dziedzinie. Z Twojego przykładu jak i na moje oko większość
logiki będzie jednak w serwisach. Rzadko kiedy (tak przynajmniej
wynika z mojego małego doświadczenia) "prawdziwa" logika dotyczy tylko
jednej encji/ obiektu dziedziny: raczej spina pare różnych. A wtedy
trzeba ją umieścić w serwisach, a nie w dziedzinie, bo przecież logika
dziedzinowa może operować tylko na jednym obiekcie swojego typu.
(Nie miałem jeszcze czasu obejrzeć przykładowego programu, może to mi
trochę wyjaśni.)
Swoją drogą, z tego co rozumiem to guru DDD (Evans itd) są
zwolennikami rich domain, a nie anemic domain. Ale nigdy nie doszedłem
do tego jak oni chcą to pogodzić z single responsibility? Jak będziemy
dodawać różne funkcjonalności do klas domeny, to w końcu klasa ta,
poza trzymaniem danych, będzie miała 50 niezwiązanych ze sobą metod,
robiących kompletnie różne rzeczy?
Adam
On Mar 18, 2009, at 10:07 AM, Paweł Lipiński wrote:
> ---------------------
> Lukasz Lenart:
>> Sama idea modelu dziedziny, który jest czymś więcej niż zbiorem klas
>> mapowanych z bazy danych jest dla mnie jasna, jednak już cała otoczka
>> nie do końca, szczególnie pojęcie Serwisów. Jednak postaram się
>> pogłębić swoją wiedzę w tym temacie.
> No więc Łukaszu wyobraź sobie taką sytuację: robisz aplikację typu
> CMS, są tam autorzy, artukuły (tu tytuł, data, treść, etc), działy
> itp. I to wszystko ładnie możesz zamodelować dziedziną. A tu
> przychodzi do Ciebie prośba o dorobienie RSS do tego. W tym RSSie ma
> być nazwisko autora i tytuł artykułu. No to możesz jechać po
> dziedzinie, ale to pewnie nie będzie zbyt wydajne, albo walnąć serwis,
> który zwraca ten RSS pukając bezpośrednio przez repozytorium do bazy.
> Pseudokod (więc nie czepiać się szczegółów):
> public class ArticleRssService {
> public List retrieveLastArticlesFromSection (Section section) {
> List articles =
> articleRepository.findLastArticlesFromSection(section);
> //jakieś przetworzenie listy do wersji wygodnej do wygenerowania
> RSSa
> return resultingList;
> }
> }
> public class ArticleRepository extends Repository {
> public List findLastArticlesFromSection (Section section) {
> return session
> .createQuery("select a.title, a.author.name from Article a where
> a.section=:section order by a.date desc")
> .setParameter("section", section)
> .setMaxResults(25)
> .list();
> }
> }
> Mógłbyś jednak woleć z jakiegoś powodu iść bezpośredio do dziedziny i
> wtedy serwis będzie opakowywał wywołania jakichś metod dziedziny
> (definiował jakiś algorytm dla nich) - np.:
> public class ArticleRssService {
> public List retrieveLastArticlesFromSection (Section section) {
> List articles =
> articleRepository.findLastArticlesFromSection(section);
> return createListOfRssEntries(articles);
> }
> private List createListOfRssEntries (List articles) {
> List resultingList = new ArrayList(articles.size());
> for (Article article : articles)
> resultingList.add(article.returnAsRssEntry());
> return resultingList;
> }
> }
> Inny przykład dla serwisów to np.: redaktor w tym CMSie chce dodać
> artykuł i opublikować go o 9:00 następnego dnia - ma do tego 1 klawisz
> gdzieś w UI. Więc masz serwis:
> public class ArticlePublishingService {
> public void saveAndPublish(String articleName, String articleText,
> String authorName, Date publishingDate) {
> Article article = createArticleAggregate(articleName, articleText,
> authorName);
> articleRepository.save(article);
> publisher.publish(article, publishingDate);
> }
> private Article createArticleAggregate(String articleName, String
> articleText, String authorName) {
> Article article = articleFactory.createArticle(articleName,
> articleText);
> Author author = authorRepository.findByName(authorName);
> author.setLastArticleDate(new Date());
> article.setAuthor(author);
> return article;
> }
> }
> Z resztą ten publisher to pewnie też jakiś serwis, który jest wołany
> np. z Quartza co minutę i publikuje wszystkie artykuły do
> opublikowania w tym czasie.
> To typowy przykład funkcjonalności nie za bardzo odpowiadającej
> dziedzinie.
> Co do HelloWorld, to może coś napiszę jak znajdę czas w weekend, tym
> bardziej, że jak widzę jest większe zapotrzebowanie na to :)
> Co do DP meetingu, to też pewnie można by się dogadać.
> ---------------------
> Jakub Nabrdalik:
> 1) Nie tylko nie mówiłem, że z serwisów nie powinno być dostępu do
> repozytorium, ale nawet explicite powiedziałem, że jak najbardziej
> powinien być (patrz mój przykład z CMSem dla Łukasza powyżej). Diagram
> pokazywał warstwy a nie wszystkie możliwe zależności, i chyba po
> prostu był mylący. Dostęp do repozytorium z serwisów jest zwykle
> niezbędny (skąś w końcu trzeba te obiekty dziedziny na których
> pracujemy uzyskać: albo z repozytorium jak już tam są, albo z fabryki
> jeśli tworzymy nowe).
> 2) Fabryki są do tworzenia nowych obiektów dziedziny i powinny być
> używane wyłącznie jeśli jest taka potrzeba (tzn konstrukcja tych
> obiektów jest złożona - powiedzmy heurystycznie, że więcej niż 2
> linijki). Głównie dotyczy to więc agregatów. Tak jak mówiłem, fabryki
> powstają zwykle jako skutek refaktoryzacji 'extract method' gdy
> tworzenie nowych obiektów/agregatów jest złożone.
> 3) Agregaty mają nam ułatwić zarządzanie grafami encji tak, by nie
> było niezbędne za każdym razem zarządzanie każdą encją oddzielnie. DDD
> nie jest związane z hibernatem, więc nie zakłada istnienia takich
> mechanizmów. Ale skoro już je mamy, to część sprawy jest załatwiona
> (przynajmniej relacje i cascading). Tak czy siak można sobie wyobrazić
> sytuację, w której różne zestawy encji o wspólnym ich podzbiorze muszą
> być zarządzane wspólnie (tzn każdy taki agregat oddzielnie) i
> statyczne deklarowanie sposobu ładowania i relacji w hibernacie może
> nie wystarczać. Np. w jednym widoku w aplikacji potrzebujemy danych
> autor 1-* artykuł *-* sekcja
> a w innym
> autor *-1 miasto 1-filia_firmy
> Wspólny jest tu tylko autor. Nie chcemy ładować artykułów autora przy
> wyszukiwaniu wszystkich autorów z jakiegoś miasta, ani czytelnika
> artykułu nie interesuje (a więc do jego widoku nie powinno być
> ładowane) w jakiej filii naszej firmy dany autor jest zatrudniony.
> Stąd różne agregaty mimo wspólnego podzbioru encji.
> ---------------------
> Tomek Szymański:
> Do odpowiedzi Maćka dodam jeszcze jedno: rzuć okiem na kod powyżej
> (ten dla Łukasza) i porównaj go z bardziej popularną/klasyczną wersją
> (tak, jestem tu trochę tendencyjny):
> @Session
> public class ArticlePublishingBean {
> public void saveAndPublish(String articleName, String articleText,
> String authorName, Date publishingDate) {
> Article ar = new Article(articleName, articleText);
> Author au = em.createQuery("from Author a where
> a.name=:name).setParameter("name",authorName).uniqueResult();
> ar.setAuthor(au);
> em.persist(ar);
> SchedulerFactory schedFact = new
> org.quartz.impl.StdSchedulerFactory();
> Scheduler sched = schedFact.getScheduler();
> sched.start();
> JobDetail jobDetail = new JobDetail("publisher", null,
> ArticlePublisher.class);
> Trigger trigger =
> TriggerUtils.makeMinutelyTrigger("publishTrigger");
> sched.scheduleJob(jobDetail, trigger);
> }
> }
> Kod powyżej jest bardzo typowy dla większości aplikacji - robi wiele
> rzeczy w jednej metodzie (w końcu to fasada), nie ma rozróżnienia
> poziomu abstrakcji operacji. Jest po prostu płaski - typowo
> proceduralny i do tego nieporozbijany na drobniejsze metody. Sam
> refactoring w postaci wydzielenia schedulowania, tworzenia encji i
> zapisu do oddzielnych metod wiele nie poprawi, bo dalej klasa będzie
> miała wiele różnych odpowiedzialności (patrz wpis na moim blogu na
> temat Single Responsibility Principle dot. klas). Jak pododajesz
> jeszcze 5-6 takich funkcjonalności, klasa będzie miała 25 różnych
> metod a po roku przestanie być utrzymywalna. W przypadku podziału
> warstw odpowiedzialności są rozłożone warstwami (odpowiednio: serwisy,
> dziedzina, repozytorium) i ten problem znika.
> ------------------
> A tak w ogóle to dziękuję za miłe przyjęcie i recenzje :)
> Jednak się przyczepię, czy nie lepiej jednak by było upchnąć taką > metodę w dziedzinie (Section.getArticlesAsRSS() ?), która wywołuje > metody ArticleRssService? Chodzi mi o ten konkretny przypadek, wtedy > UI, klient, etc. nie ma pojęcia o serwisie.
I w ten sposób łamiesz konwencję JavaBean i tymsamym udowadniasz, że nie masz racji :-P Dziedzina jest warstwowo poniżej serwisów więc nie ma prawa się do nich dobierać. Nie chodzi mi tu o ideologię, tylko prostą zasadę, że zależności tylko "w dół" która zapewnia odpowiedni podział poziomów abstrakcji i zwiększa czytelność kodu. W jaki sposób dostaniesz te artykuły (czy przez relację z sekcją, czy bezp. zapytaniem) ma mniejsze znaczenie i tu faktycznie można by pewnie dyskutować.
> Z Twojego przykładu jak i na moje oko większość > logiki będzie jednak w serwisach.
Bo przykład jest strywializowany. Ale częściowo masz rację. Serwisy są proceduralne, bo realizują pewien algorytm. Dziedzina jest obiektowa, bo realizuje relacje i operacje na samych tych obiektach.
> Rzadko kiedy (tak przynajmniej > wynika z mojego małego doświadczenia) "prawdziwa" logika dotyczy tylko > jednej encji/ obiektu dziedziny: raczej spina pare różnych. A wtedy > trzeba ją umieścić w serwisach, a nie w dziedzinie, bo przecież logika > dziedzinowa może operować tylko na jednym obiekcie swojego typu.
Nie zgodzę się z ostatnim zdaniem. Logika dziedzinowa może dotyczyć obiektów które są w relacji z 'this'. Tzn. może wywoływać ich metody. Idealnie oczywiście tylko metody obiektów będących w bezpośredniej relacji (a te zagłębiają się dalej). Serwisy (choć to trochę zależnie od technologii) wchodzą wtedy gdy operacje dotyczą nieobiektowych zachowań albo dotyczą niepowiązanych ze sobą obiektów (artykuł <-> publisher w przykładzie powyżej)
> Swoją drogą, z tego co rozumiem to guru DDD (Evans itd) są > zwolennikami rich domain, a nie anemic domain. Ale nigdy nie doszedłem > do tego jak oni chcą to pogodzić z single responsibility? Jak będziemy > dodawać różne funkcjonalności do klas domeny, to w końcu klasa ta, > poza trzymaniem danych, będzie miała 50 niezwiązanych ze sobą metod, > robiących kompletnie różne rzeczy?
Nie rozumiem chyba pytania. SRP nie jest w sprzeczności z obiektowością (zwaną tu rich domain) tylko jest regułą jej realizacji. Jeśli twój obiekt ma tak wiele zachowań, to powinny być one umieszczone w nim właśnie. One nie będą ze sobą 'niezwiązane', wręcz odwrotnie wszystkie są związane przynależnością do tego samego obiektu. To że umiesz chodzić i pić nie oznacza, że musisz być 2 różnymi osobami. SRP w przypadku klas dotyczy tylko tego, żeby klasa zawierała metody faktycznie do niej przynależące, czyli klasa Article ma metody dotyczące artykułu a nie np. wysyłania maili z potwierdzeniem opublikowania.
W dniu 18 marca 2009 10:37 użytkownik Paweł Lipiński <lipinski.pa...@gmail.com> napisał:
> I w ten sposób łamiesz konwencję JavaBean i tymsamym udowadniasz, że > nie masz racji :-P
Znaczy, że co, że nie można tego edytować za pomocą wizualnych edytorów? “A Java Bean is a reusable software component that can be manipulated visually in a builder tool.”
A jeśli chodzi o nazwę metody to listArticlesAsRSS() będzie lepsze po nie rozumiem.
> Dziedzina jest warstwowo poniżej serwisów więc nie ma prawa się do > nich dobierać. Nie chodzi mi tu o ideologię, tylko prostą zasadę, że > zależności tylko "w dół" która zapewnia odpowiedni podział poziomów > abstrakcji i zwiększa czytelność kodu.
Czyli serwisy operują na dziedzinie, która to jest zbiorem POJO? Czyli tak zwany anemiczny model? Czegoś tu nie rozumiem ;-(
> W jaki sposób dostaniesz te artykuły (czy przez relację z sekcją, czy > bezp. zapytaniem) ma mniejsze znaczenie i tu faktycznie można by > pewnie dyskutować.
W twoim przykładzie przekazałeś Section do serwisu i dla mnie było jasne, że Section zawiera listę powiązanych z nią artykułów i w sumie teraz tak sobie myślę, że ten serwis jest zbędny. Klasa Article ma metodę returnAsRssEntry(), pozostaje tylko wybrać odpowiednią listę tych artykułów (najnowsze 10) co załatwi repozytorium.
> --------------------- > Jakub Nabrdalik: > 1) Nie tylko nie mówiłem, że z serwisów nie powinno być dostępu do > repozytorium, ale nawet explicite powiedziałem, że jak najbardziej > powinien być (patrz mój przykład z CMSem dla Łukasza powyżej). Diagram > pokazywał warstwy a nie wszystkie możliwe zależności, i chyba po > prostu był mylący. Dostęp do repozytorium z serwisów jest zwykle > niezbędny (skąś w końcu trzeba te obiekty dziedziny na których > pracujemy uzyskać: albo z repozytorium jak już tam są, albo z fabryki > jeśli tworzymy nowe).
Dzięki, to mi bardzo rozjaśniło. Wychodziłem z założenia, że tylko obiekty domenowe mają dostęp do repozytoriów/dao, ale w sumie faktycznie lepiej żeby istniały podstawowe interfejsy wyciągania obiektów implementowane przez repozytoria i wykorzystywane przez serwisy. "Podstawowe" bo dam sobie rękę uciąć, że gdyby znalazło się tam za dużo metod (save/persist, delete etc.) któryś z programistów w zespole zacznie z powrotem umieszczać logikę w serwisach.
> 2) Fabryki są do tworzenia nowych obiektów dziedziny i powinny być > używane wyłącznie jeśli jest taka potrzeba (tzn konstrukcja tych > obiektów jest złożona - powiedzmy heurystycznie, że więcej niż 2 > linijki). Głównie dotyczy to więc agregatów. Tak jak mówiłem, fabryki > powstają zwykle jako skutek refaktoryzacji 'extract method' gdy > tworzenie nowych obiektów/agregatów jest złożone.
Ok, teraz ogarniam. Od kilku lat unikam fabryk jak ognia - kojarzą mi się z programowanie z przed ery IoC, ale w opisanych przez Ciebie sytuacjach mają sens.
> 3) Agregaty mają nam ułatwić zarządzanie grafami encji tak, by nie > było niezbędne za każdym razem zarządzanie każdą encją oddzielnie. DDD > nie jest związane z hibernatem, więc nie zakłada istnienia takich > mechanizmów. Ale skoro już je mamy, to część sprawy jest załatwiona > (przynajmniej relacje i cascading). Tak czy siak można sobie wyobrazić > sytuację, w której różne zestawy encji o wspólnym ich podzbiorze muszą > być zarządzane wspólnie (tzn każdy taki agregat oddzielnie) i > statyczne deklarowanie sposobu ładowania i relacji w hibernacie może > nie wystarczać. Np. w jednym widoku w aplikacji potrzebujemy danych > autor 1-* artykuł *-* sekcja > a w innym > autor *-1 miasto 1-filia_firmy > Wspólny jest tu tylko autor. Nie chcemy ładować artykułów autora przy > wyszukiwaniu wszystkich autorów z jakiegoś miasta, ani czytelnika > artykułu nie interesuje (a więc do jego widoku nie powinno być > ładowane) w jakiej filii naszej firmy dany autor jest zatrudniony. > Stąd różne agregaty mimo wspólnego podzbioru encji.
To również rozjaśnia . Dodaj te trzy punkty do swojej prezentacji, gdy będziesz znów gdzieś występował - bardzo się przydadzą.
> Znaczy, że co, że nie można tego edytować za pomocą wizualnych edytorów? > "A Java Bean is a reusable software component that can be manipulated visually > in a builder tool."
> A jeśli chodzi o nazwę metody to listArticlesAsRSS() będzie lepsze po > nie rozumiem.
No oczywiście, że się tej nazwy czepnąłem. Ale jasne, że to tylko przykład, stąd ta emotikonka przy moim komentarzu.
> Czyli serwisy operują na dziedzinie, która to jest zbiorem POJO? Czyli > tak zwany anemiczny model? Czegoś tu nie rozumiem ;-(
Zaraz zaraz. Anemiczny model to brak modelu, ew. obiekty jako struktury danych bez zachowań. Serwisy operują tu na pełnych obiektach, czyli tzw. rich model.
> W twoim przykładzie przekazałeś Section do serwisu i dla mnie było > jasne, że Section zawiera listę powiązanych z nią artykułów i w sumie > teraz tak sobie myślę, że ten serwis jest zbędny. Klasa Article ma > metodę returnAsRssEntry(), pozostaje tylko wybrać odpowiednią listę > tych artykułów (najnowsze 10) co załatwi repozytorium.
To ja tylko zaznaczę, że kod był wymyślony ad hoc i walnięty prosto do maila, więc te szczegóły są zupełnie nieistotne. Wersja z returnAsRssEntry wewn. klasy Article jest ok. dla 10 elementów. Jeśli miałbyś ich zwrócić 10 000 to byś zamordował serwer. Wtedy wali się z serwisu prosto przez repozytorium odpowiednim zapytaniem tylko po wybrane dane i się je bez materializowania w obiekty zwraca do wrzucenia do RSSa. Przerabiałem więc wiem :) To z resztą dobry przykład zarówno na refactoring architektury (ta sama funkcjonalność innym sposobem) jak i na użycie serwisu z pominięciem dziedziny.
W dniu 18 marca 2009 12:17 użytkownik Paweł Lipiński <lipinski.pa...@gmail.com> napisał:
>> A jeśli chodzi o nazwę metody to listArticlesAsRSS() będzie lepsze po >> nie rozumiem. > No oczywiście, że się tej nazwy czepnąłem. Ale jasne, że to tylko > przykład, stąd ta emotikonka przy moim komentarzu.
już się bałem, że do reszty zgłupiałem :P
> [ciach]
Ok, wszystko jasne, czyli to jest to o czym zawsze myślałem a nie zawsze udało mi się wdrożyć ;-)
>> Rzadko kiedy (tak przynajmniej >> wynika z mojego małego doświadczenia) "prawdziwa" logika dotyczy >> tylko >> jednej encji/ obiektu dziedziny: raczej spina pare różnych. A wtedy >> trzeba ją umieścić w serwisach, a nie w dziedzinie, bo przecież >> logika >> dziedzinowa może operować tylko na jednym obiekcie swojego typu. > Nie zgodzę się z ostatnim zdaniem. Logika dziedzinowa może dotyczyć > obiektów które są w relacji z 'this'. Tzn. może wywoływać ich metody. > Idealnie oczywiście tylko metody obiektów będących w bezpośredniej > relacji (a te zagłębiają się dalej).
> Serwisy (choć to trochę zależnie od technologii) wchodzą wtedy gdy > operacje dotyczą nieobiektowych zachowań albo dotyczą niepowiązanych > ze sobą obiektów (artykuł <-> publisher w przykładzie powyżej)
>> Swoją drogą, z tego co rozumiem to guru DDD (Evans itd) są >> zwolennikami rich domain, a nie anemic domain. Ale nigdy nie >> doszedłem >> do tego jak oni chcą to pogodzić z single responsibility? Jak >> będziemy >> dodawać różne funkcjonalności do klas domeny, to w końcu klasa ta, >> poza trzymaniem danych, będzie miała 50 niezwiązanych ze sobą metod, >> robiących kompletnie różne rzeczy? > Nie rozumiem chyba pytania. SRP nie jest w sprzeczności z > obiektowością (zwaną tu rich domain) tylko jest regułą jej realizacji. > Jeśli twój obiekt ma tak wiele zachowań, to powinny być one > umieszczone w nim właśnie. One nie będą ze sobą 'niezwiązane', wręcz > odwrotnie wszystkie są związane przynależnością do tego samego > obiektu. To że umiesz chodzić i pić nie oznacza, że musisz być 2 > różnymi osobami. SRP w przypadku klas dotyczy tylko tego, żeby klasa > zawierała metody faktycznie do niej przynależące, czyli klasa Article > ma metody dotyczące artykułu a nie np. wysyłania maili z > potwierdzeniem opublikowania.
Obiektowość a rich domain to chyba jednak co innego. Nie widzę sprzeczności SRP z obiektowością, tylko właśnie z rich domain. Nie podoba mi się umieszanie wszystkich (lub prawie wszystkich) zachowań obiektów w jednej klasie. Jeżeli mam np. konwersję artykułów do RSSa i do PDFa, to jak dla mnie to powinno być w osobnych klasach.
Ale to pewnie też w dużej mierze kwestia gustu - ja chyba wolę traktować dane "dziedzinowe" bardziej jakby były typami algebraicznymi (czy jak to się tłumaczy: algebraic data type, czyli pattern matching i te sprawy). Obiektowość oczywiście też, ale gdzie indziej, i nie wszędzie :). Chociaż często w Javie tego się nie da elegancko załatwić - ale w innych językach może już być lepiej, jak mamy choćby pattern matching w scali czy extension methods).
Co do analogii z człowiekiem, to człowieka się ani nie rozszerza o nowe funkcje, ani nie modyfikuje istniejących zbyt często ;)
Witam, ciekawy temat i prezentacja. mam tylko male pytanie jak realizujesz dostep do repozytoriow z poziomu obiektow modelu zakladajac ze uzywamy Springa do zarzadzania lifecyclem fasad, serwisow, rezpozytoriow, a Hibernate jako mechanizm persistence dla obiektow modelu, czyli dosc standarodowo. Pytam bo choc to szczegol techonologiczny to ciezko bylo of ladne rozwiazanie pare lat temu, gdy mialem przyjemosc tworzyc takie rozwiazania na platformie java/jee.
On Mar 18, 2009, at 10:07 AM, Paweł Lipiński wrote:
> Jakub Nabrdalik: > 1) Nie tylko nie mówiłem, że z serwisów nie powinno być dostępu do > repozytorium, ale nawet explicite powiedziałem, że jak najbardziej > powinien być (patrz mój przykład z CMSem dla Łukasza powyżej). Diagram > pokazywał warstwy a nie wszystkie możliwe zależności, i chyba po > prostu był mylący. Dostęp do repozytorium z serwisów jest zwykle > niezbędny (skąś w końcu trzeba te obiekty dziedziny na których > pracujemy uzyskać: albo z repozytorium jak już tam są, albo z fabryki > jeśli tworzymy nowe).
> Obiektowość a rich domain to chyba jednak co innego. Nie widzę > sprzeczności SRP z obiektowością, tylko właśnie z rich domain. Nie > podoba mi się umieszanie wszystkich (lub prawie wszystkich) zachowań > obiektów w jednej klasie. Jeżeli mam np. konwersję artykułów do RSSa i > do PDFa, to jak dla mnie to powinno być w osobnych klasach.
No dobra, nie chodziło mi o to, że to to samo, ale że rich domain jest realizacją obiektowości w aplikacjach biznesowych. I dlatego nie widzę sprzeczności między SRP a rich domain. W Twoim przykładzie (konwersja do RSS i PDF) żadna z tych metod nie powinna być w Article, bo to pogwałca SRP. Ale również do jest smell pt. 'feature envy' - klasa chce robić za dużo. Za konwersję odpowiedzialny powinien być konwerter (tam gdzieś wcześniej pisałem też o wysyłaniu maili notyfikacyjnych o powstaniu artykułu, co również dotyczy Article, ale nie powinno w niej być umieszczone). Dlatego ja bym specjalnie nie rozróżniał między obiektowością a rich domain.
> Witam, > ciekawy temat i prezentacja. mam tylko male pytanie jak realizujesz > dostep do repozytoriow z poziomu obiektow modelu zakladajac ze uzywamy > Springa do zarzadzania lifecyclem fasad, serwisow, rezpozytoriow, a > Hibernate jako mechanizm persistence dla obiektow modelu, czyli dosc > standarodowo. Pytam bo choc to szczegol techonologiczny to ciezko bylo > of ladne rozwiazanie pare lat temu, gdy mialem przyjemosc tworzyc > takie rozwiazania na platformie java/jee.
Teraz w Springu (od v2.5 o ile pamiętam) jest już trochę łatwiej, bo można obiekty dziedzinowe zadnotować jako @Configurable i wszczepić w nie co się chce. Wcześniej załatwiałem to ręcznie za pomocą aspectj'a.
>> Obiektowość a rich domain to chyba jednak co innego. Nie widzę >> sprzeczności SRP z obiektowością, tylko właśnie z rich domain. Nie >> podoba mi się umieszanie wszystkich (lub prawie wszystkich) zachowań >> obiektów w jednej klasie. Jeżeli mam np. konwersję artykułów do >> RSSa i >> do PDFa, to jak dla mnie to powinno być w osobnych klasach. > No dobra, nie chodziło mi o to, że to to samo, ale że rich domain jest > realizacją obiektowości w aplikacjach biznesowych. I dlatego nie widzę > sprzeczności między SRP a rich domain. W Twoim przykładzie (konwersja > do RSS i PDF) żadna z tych metod nie powinna być w Article, bo to > pogwałca SRP.
Ok, to się nawet zgadzamy :)
> Ale również do jest smell pt. 'feature envy' - klasa > chce robić za dużo. Za konwersję odpowiedzialny powinien być konwerter > (tam gdzieś wcześniej pisałem też o wysyłaniu maili notyfikacyjnych o > powstaniu artykułu, co również dotyczy Article, ale nie powinno w niej > być umieszczone).
I wtedy Article wie jak stworzyć konwerter? Ew. fabrykę konwerterów? Czy serwis?
To czego za bardzo nie rozumiem w DDD, to rozdzielenie logiki na dwa miejsca: serwisy i obiekty dziedziny. I tego właśnie dotyczą moje przykłady - nie za bardzo wiem, jaka logika miałaby się znajdować w dziedzinie.
> To czego za bardzo nie rozumiem w DDD, to rozdzielenie logiki na dwa > miejsca: serwisy i obiekty dziedziny. I tego właśnie dotyczą moje > przykłady - nie za bardzo wiem, jaka logika miałaby się znajdować w > dziedzinie.
No to od innej strony: To jest taka różnica jak między testami jednostkowymi i funkcjonalnymi. Tak +/-: testy jednostkowe testują logikę dziedziny, testy funkcjonalne na poziomie serwisów.
Weźmy ten wymęczony przeze mnie przykład z CMSem. Masz artykuły, one mają autorów, czytelników, którzy mogą zostawiać komentarze itp. Teraz w klasie Article będą pola typu tytuł, treść, data dodania, data publikacji, etc. Metody będą zarządzały relacjami (typu dodajKomentarz(Komentarz k)) ale również realizowały funkcjonalności na poziomie pojedynczego artykułu. Np.: - countStatistics() licząca słowa, zdania, itp (np. na potrzeby określenia płacy autora) - abstract() zwracająca skrót artykułu (np. tytuł + pierwsze 10 zdań) - language() zwracająca język artykułu np. na podst. naiwnego klasyfikatora bayesowskiego itp itd. I teraz jeśli będziesz miał różne typy artykułów (zwykły tekst, fotorelacja, itp) to mogą być różne implementacje tych metod - w przypadku umieszczenia ich w jakimś serwisie musiałbyś mnożyć serwisy albo wstawiać w tych metodach if (article.getType() == Article.TEXT) {...} else ... i podobne kwiatki (blueeee)
Serwisy realizują wtedy funkcjonalności bardziej end-2-end. Np.: chcesz na raz zapisać artykuł, wrzucić go do publishera a jego statystyki i język do hurtowni danych.
> To czego za bardzo nie rozumiem w DDD, to rozdzielenie logiki na dwa > miejsca: serwisy i obiekty dziedziny. I tego właśnie dotyczą moje > przykłady - nie za bardzo wiem, jaka logika miałaby się znajdować w > dziedzinie.
Rozgryzam właśnie przykład dddsample i nasuwa mi się odpowiedź. Paweł, wypowiedz się proszę, czy to ma sens, czy dobrze to rozumiem, czy też pieprzę głupoty.
Przykład wyróżnia dwa rodzaje serwisów: te należące do domeny i te należące do aplikacji (wzorzec fasady).
To co wystawia funkcje biznesowe dla warstwy view (controller + view) to SERWISY APLIKACJI (wzorzec fasady i NIC więcej; żadnej logiki).
W SERWISACH DZIEDZINY siedzi część logiki biznesowej (Te serwisy należą do dziedziny)
Pytanie było, która część (jaka logika) leży w serwisach dziedziny?
Oto odpowiedź:
"Domain services encapsulate domain concepts that just are not naturally modeled as things."
Jeśli fragment logiki biznesowej (np. rejestracja użytkownika) pasuje do jakiejś klasy dziedziny, należy ją tam koniecznie umieścić (user.register()).
Jeśli nie pasuje do żadnej klasy dziedziny, należy ją wrzucić w serwis dziedziny (userService.deleteNotActivatedUsers()).