Pokazywanie postów oznaczonych etykietą OOA. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą OOA. Pokaż wszystkie posty

niedziela, 11 stycznia 2009

Dystans

W życiu trzeba mieć dystans - to podstawa. W programowaniu też - szczególnie do tego co mówi klient:)

Dalsze me dywagacje pierwej podeprę takim oto małym przykładzikiem z frontu (projektu w jakim do niedawna jeszcze brałem udział):

Emulując analityka radośnie przeprowadzałem analizę małego modułu do zarządzania drukami ścisłego zarachowania. Z analizy biznesowej wynikało jakoby:

W jednym dziale panie pracują z DPDANPB_1 (Drukiem Potrzebnym Do Arcy Nudnego Procesu Biznesowego 1); druki przychodzą z drukarni do działu w paczkach po parę tysięcy sztuk; użytkownicy dzielą paczki na bloczki po kilkadziesiąt sztuk.

W innym dziale panie pracują z DPDANPB_2, które to przychodzą z drukarni również w paczkach - tym razem po kilkaset sztuk. Użytkownicy w tym dziale nie dzielą sobie druczków na bloczki - po prostu radośnie wykonują swą jakże smutną pracę używając druczków DPDANPB_2.

W obu przypadkach system musi ogólnie rzecz ujmując nadzorować druczki (zliczać, kontrolować kto komu ile po co wydał itp) z uwagi na to iż są one jak już wspomniałem "ściśle zarachowane" - cóż za małostkowość swoją szosą;)


Zatem na tak zwany rzut tak zwanego oka widać, że mamy do czynienia z taką oto strukturą (wybaczcie ASCII art, ale w niedzielny poranek nie chce mi się odpalać kombajnu - do UMLa):

Paczka1 ------* Bloczek1 -------* DPDANPB_1
Paczka2 ------* DPDANPB_2

Wszystko jasne i oczywiste, odpalamy Eclipse, tworzymy 5 klasek, adnotujemy @Entity, radośnie klepiemy atrybuty i gotowe.

Za parę miesięcy ktoś będzie się martwić o zmiany bo na przykład: nowa kierowniczka działu 2 nie będzie chciała być gorsza i wprowadzi u siebie również dwustopniowy podział. Następnego dnia kierowniczka działu 1 chcąc pozostać lepszą (od tej ździry z dwójki) wprowadzi trójstopniowy podział. Albo - o zgrozo - będąc w fazie cyklu gdy w jej ustroju dominują męskie hormony wprowadzi 8 stopniowy podział - BO TAK!!!

Możemy oczywiście założyć, że nie zależy nam na przygotowaniu giętkiego systemu otwartego na nowe wymagania, np: atak kosmitów lub co gorsza nierokendrolowe dni kierowniczek. Nie chcemy wydawać kasy na dobry projekt, odwalamy chałę, wdrażamy metodykę RÓB! Takie założenie biznesowe jest w sumie fajne, bo zwalnia mózg developerów od myślenia a co za tym idzie chroni go przed śmiertelnym przegrzaniem - hehe krótkowzroczni biedacy nie widzą, że do czasu...

Jak się jednak okazało (zresztą nie tylko w tym przypadku), że chwila zastanowienia nic nie kosztuje i jest inwestycją pozwalającą zaoszczędzić niemałe pieniądze już w niedalekiej przyszłości.

Wystarczy się nieco wnikliwej przyjrzeć zachowaniu klienta - dokonać rzetelnej analizy. Użytkownicy z działu 1 wiedzeni wrodzonym instynktem niemal intuicyjnie podsuwają nam rozwiązanie: stary dobry model "plików w katalogu".

Uogólniając mamy po prostu jakieś dokumenty pogrupowane w drzewiaste struktury katalogów: katalog zawiera inne katalogi oraz dokumenty:

Katalog -------* Dokument
 |      *
 |----|

Jeszcze 2 kwestie z perspektywy projektanta OO:

W razie potrzeby konkretne druczki (DPDANPB_1, DPDANPB_2,...) mogą dziedziczyć po dokumencie jeżeli chcemy dodać im specyficzną odpowiedzialność biznesową - anemicznemu modelowi mówimy stanowcze NIE. Dziedziczenie jest akurat w tym wypadku usprawiedliwione ponieważ dokumenty będą traktowane polimorficzne na przykład w logice nadzorowania - zatem jesteśmy zgodni z zasadą podstawienia Liskov - możesz używać dziedziczenia tylko wówczas gdy spodziewasz się polimorfizmu.
A co do technicznych aspektów persystencji naszych dokumentów polecam artykuł Dawida Walczaka: JPA - modelowanie dziedziczenia.


Co natomiast ze szczególnymi typami katalogów (węzłów drzewa)? Tu akurat dziedziczenie niezbyt mi się podoba - nie wiem dlaczego.
Zamiast dziedziczenia zalecałbym przede wszystkim podpinanie pod nie strategii.
Czyli nasz katalog mógłby mieć np metodę validate(), która to deleguje proces walidacji do konkretnego walidatora - oczywiście stojącego za interfejsem strategii.

Oczywiście w razie gdy pewien rodzaj katalogu ma specyficzną odpowiedzialność, której nie mają inne to nie możemy pozwolić na "brudzenie" klasy katalogu jakąś ezoteryczną metodą. Drugim (nie wykluczającym rozwiązaniem) pozwalającym na "dodanie" nowych odpowiedzialności do klasy Katalogu jest wzorzec wizytatora. Czyli w tym przypadku katalog miałby metodę pozwolSieZwintestygowac(Visitor v).

//=============================
To był bardzo szczególny przykład - natomiast ogólna idea jest taka: klient zazwyczaj mówi do nas obiektami (instancjami) biznesowymi. Rola analizy jest między innymi taka aby odkryć klasy tych obiektów. W naszym przykładzie klient posługiwał się pojęciami paczka1, paczka2, bloczek. Naiwna analiza przekłada je na klasy. Natomiast w rzeczywistości są to pewne szczególne obiekty klasy Katalog.

W sumie to nie bez powodu w UML istnieje diagram obiektów.


Jeszcze bardziej ogólną analizę problemów analizy przedstawił Michał Bartyzel w swym poście "Metaprogramy w tworzeniu oprogramowania" (wytłuszczenia są moje):

[Klienci] Opisują to jednak w postaci procesów, natomiast analityk widzi system jako strukturę. Podążając za procesem łatwiej dokonywać w nim zmian. Z kolei zmiany wprowadzane w strukturze wymagają przeanalizowania całej koncepcji od nowa. Podczas zbierania wymagań klient często zastanawia się nad pożądanym sposobem pracy. Próbuje różnych alternatywnych przebiegów tego samego procesu i oczekuje od analityka pomocy w wyborze najlepszego rozwiązania. Jeśli analityk nie podąża w procesie za klientem, lecz percypuje system jako strukturę, to ma nie lada kłopot. Alternatywne ścieżki procesu klienta zazwyczaj mają istotny wpływ na domniemaną strukturę systemu i/lub implementację.


Polecam lekturę całego posta - jest arcy ciekawy, bo zanurzony w kontekście Metaprogramów myślenia, o których wspomniałem już 2 krotnie:
- The Unforgiven
- Twoja rola w zespole

Polecam też oczywiście całego bloga Michała Bartyzela - jest zanurzony w różnych innych ciekawych kontekstach.

wtorek, 18 listopada 2008

UP-DDD in Action: Planowanie jest nieodzowne, Sir!

Prezydent Dwight Eisenhower tak oto wspominał czasy gdy był generałem armii amerykańskiej:
"In preparing for battle I have always found that plans are useless, but planning is indispensable."

Dziś kolejny post z serii Unified Process & Domain Driven Design in Action. Prace nad projektem wciąż w fazie analizy biznesowej więc post będzie raczej wypełniaczem (podtrzymującym serię) o charakterze "filozoficznym".

Jaką rolę w projekcie spełnia UML (dokumentacja w ogólności)? Są różne podejścia...

Może na przykład nie mieć żadnej roli. Organizacja może radośnie postępować według metodyki chałupniczo-garażowej. Metodyka ta zakłada 3-etapowy proces:

radosna twórczość - po podpisaniu kontraktu następuje pięciominutowa odprawa sześciopaku programistów po czym rzucają się oni na klawiaturę (jak nie przymierzając marynarz atomowego okrętu podwodnego na kozę w porcie) i klepanie na wyścigi. Nikt nie wiec po co, na co i jak, ale to nie szkodzi... byle się wyżyć.

kosy racławickie - wiadomo... burdel rośnie, projekt się sypie, co raz jakaś eskalacja. Najlepszym rozwiązaniem jest pospolite ruszenie: nadgodziny + "ochotnicy" do pomocy. Jak wiemy z doświadczenia jest to oczywisty antywzorzec zarządzania ponieważ nowi ludzie jedynie spowalniają pracę, ale kto by się nad tym zastanawiał - przecież każdy brakujący diagram UML można zastąpić skończoną liczbą studentów...

stawanie na kancie tego no... nosa! - tydzień do release. Ludzie funkcjonują w trybie zombie. Każdy brakujący diagram UML można zastąpić skończoną ilością sznurka do snopowiązałki...

Jeżeli opisana metodyka chałupniczo-garażowa sprawdza się wyśmienicie - na co wskazuje na przykład sam fakt istnienia organizacji (że o systematycznym zwiększaniu zysków nie wspomnę) to oczywiście jak najbardziej jest słuszna i nie powinno się jej zmienić bo jeszcze kruchy stan równowagi się posypie. Biznes to biznes - doskonale to rozumiem.

Na drodze logicznego rozumowania można z czasem uświadomić sobie, że jednak wydanie paru groszy na dokumentację zwróci się wielokrotnie w przyszłości. Metodyka chałupniczo-garażowa owszem przynosi świetne dochody, ale okazuje się, że mogłaby przynosić jeszcze większe gdyby nie tracić czasu na zmaganie się z burdelem i po prostu udokumentować wszystko. Pycha pomysł!

I tu pojawia się kolejna możliwa rola dokumentacji: kula u nogi. Po radosnym zaimplementowaniu trzeba jeszcze zrobić te cholerne rysuneczki i posiedzieć nad wordem. A tu nad głową stoi poganiacz i zadaje najgłupsze pytanie jakie istnieje: "na kiedy będzie gotowy następny moduł?". Na szczęście wspaniałe narzędzia mają funkcję wstecznej inżynierii - dwa kliki i zestaw pięknych prostokątów gotowy. Teraz jeszcze tylko wkleić to jakoś do worda, dokleić jakiś schemat bazy i z dyni. Hmmm nie tak szybko... za mało tej dokumentacji... ale wystarczy wkleić jeszcze trochę kodu źródłowego i bedzie. No dobra, nie odwalajmy chały - tylko same nagłówki klas i metod. Jak wciąż mało to można dokleić trochę reguł biznesowych z jakiś roolsów. Ktoś kazał robić dokumentację to się zrobi - "będzie Pan zadowolony"

Istnieją różne dewiacje w tym podejściu. Można na przykład analizą nazwać projekty ekranów - co prowadzi do ciekawej paranoi. Innym skrzywieniem jest traktowanie schematu bazy danych jako projektu systemu - również ciekawa paranoja.

W sumie to dokumentacja tworzona po fakcie ma jednak swoją wartość - oczywiście pod warunkiem, że ktoś ją aktualizuje:P



A gdyby tak potraktować diagramy UML jako narzędzie do eksperymentowania? Nie chodzi o sam fakt posiadania dokumentacji, ale proces jej tworzenia. Wysiłek umysłowy włożony w zastanowienie się o co w ogóle chodzi. Skupienie się na tym CO a nie JAK. Eksperymentowania, które polega na najpierw na notowaniu wyników analizy biznesowej. Później poddaniu ich wnikliwej i rzetelnej analizie systemowej, czyli odfiltrowaniu chaosu wprowadzone przez ekspertów biznesowych i wyciągnięciu z tego czystego ekstraktu. W wyniku otrzymamy diagramy Use Caseów i Obiektów Biznesowych - stąd już blisko do warstw logiki aplikacji i biznesowej. I nie chodzi bynajmniej o to aby jedynie przepisać bełkot klienta na jajeczka z ludzikami i cieszyć się, że mamy diagramiki Use Case. Tworząc diagramy warto się zastanowić nad każdą kreską i jajeczkiem - czy dany element ma sens, czy nie da się czegoś uogólnić i wyłonić reużywalnej abstrakcji, czy nie da się tego zrobić prościej - właśnie o to upraszczanie chodzi. Upraszczanie na etapie "eksperymentowania" w UML zwróci się na etapie kodowania.

Analogiczne eksperymenty w UML można przeprowadzać na etapie projektowania. Zanim zacznie się kodować warto przez chwilę popatrzeć z większej wysokości na całość bo można przypadkiem zauważyć uogólnienie prowadzące całkiem za darmo do reużywalności. Można też całkiem przypadkiem znaleźć miejsca dla jakiegoś wzorca projektowego i całkiem za darmo zapewnić w przyszłości większą giętkość systemu i jego otwartość na rozbudowę.

Z samego planowania może wyjść więcej dobrego niż z posiadania gotowych (wygenerowanych) planów...
No i jak by nie było - efektem ubocznym tworzenia dokumentacji jest jej posiadanie:)



//=================
UML sam w sobie jest po prostu jakimś tam pismem obrazkowym. Może ma nieco lepiej zdefiniowane reguły niż pismo jaskiniowców, ale wciąż - pismem obrazkowym;P

Sztuka stosowania UML nie polega na skrupulatnym rysowaniu prostokątów od ekierki i następnie ich kolorowaniu tak aby nie wyjść kredką poza kontur.

Nie chodzi o samą formę ale o proces myślowy, który zaszedł np. podczas obiektowej analizy domeny biznesowej. Prostokąty to tylko notatka z walki ze złożonością i chaosem.

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:)

poniedziałek, 4 sierpnia 2008

Strategia na wielokrotne dziedziczenie

Wielokrotne dziedziczenie jest be - wszyscy o tym wiemy. Poza oczywistymi problemami logicznymi narusza jedną z głównych zasad OO - utrzymywanie wysokiej kohezji - klasa powinna mieć jak najmniej odpowiedzialności, najlepiej jedną.

Ostatnio zmagałem się z zaprojektowaniem pewnego modelu dostarczonego z fazy analizy. Model analityczny był w sumie dosyć prosty, jednak kobieca intuicja podpowiadała, że będzie sprawiał problemy z rozbudową w przyszłości. Problem domenowy wyglądał tak: mamy zlecenie podania leku; zakładamy, że w przyszłości na pewno dojdą do wykonania inne rzeczy; możemy również zlecać wykonanie czegoś raz lub wielokrotnie (okresowo, wg zadanego wzorca powtarzania).

Model analityczny wyglądał tak: mamy zlecenie, po którym dziedziczy zleceniePodaniaLeku, po którym to z kolei dziedziczą zlecenia jednorazowegoPodaniaLeku jak i wielokrotnegoPodaniaLeku. Czujecie chyba eksplozję kombinatoryczną, która się szykuje w razie pojawienie się nowych rodzajów rzeczy do zlecenia czy nowych polityk powtarzania, lub (o zgrozo) pojawienie się nowych aspektów zlecenia.

Widać tu jak na dłoni wzorzec projektowy Strategii. Postępując zgodnie z tym wzorcem dojdziemy do modelu, gdzie zlecenie składa się ze strategii powtarzania - lub polityki idąc za terminologią Domain Driven. Zlecenie składa się też ze strategii wykonania pewnej czynności. Zlecenie "widzi" jedynie interfejsy strategii, natomiast z jego punktu widzenia implementacje nie są istotne.
Podejście to zapewni nam również zgodność z kolejną zasadą GRASP.

Wzorzec co prawda "widać" z perspektywy projektowej czy implementacyjnej, jednak z perspektywy analitycznej ponoć wygodniej jest pokazać wielodziedziczenie - po typie powtarzania i po typie czynności do wykonania. Jednak ponieważ wielodziedziczenie jest be, więc aby go uniknąć doszło na poziomie analizy do "rzutowania" aspektu powtarzania na aspekt czynności.

Sytuacja z dystansu wygląda śmiesznie: analitycy "szyfrują" rzeczywistość do jednowymiarowej przestrzeni aby uniknąć wielodziedziczenia, a projektanci muszą później dokonać "odszyfrowania" na swoje strategie;)

Dlatego też wspólnie uznaliśmy, że wielodziedziczenie jest git - ale tylko na poziomie analitycznym:P Dzięki niemu projektanci mogą niemal w sposób automatyczny dokonać transformacji modelu analitycznego na implementacyjny model obiektowy oparty na strategiach. Niemal automatyczny ponieważ wszystkie wielowymiarowe aspekty dokładnie "widać" gdy nie są spłaszczone do jednego wymiaru


//===========================
Tak właściwie to nie jestem do końca przekonany czy aby na pewno wielodziedziczenie jest immanentną cechą obiektów ze świata rzeczywistego. Być może jest to jedynie kwestia wyuczonego postrzegania; wynik bezkrytycznego powielania schematów myślowych. Pamiętam, że sam zanim przestawiłem się na agregację/kompozycję, widziałem wszędzie dziedziczenie - wszak kto ma w ręku młotek, ten wszędzie widzi gwoździe.

GRASP jest doskonale opisany w "Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development" autorstwa Craiga Larmana