piątek, 30 stycznia 2009

ORM - o co ta walka?



Jako weteran hibernowania obiektów (w służbie od wersji 2.0) pozwolę sobie dziś na nieco prowokującego posta z lekkim dystansem do mainstreamu...:)

Pomiędzy światem relacyjnym a obiektowym istnieje oczywista niezgodność paradygmatów. Wynika z tego szereg (powiedzmy delikatnie) niezręczności - dosyć wyczerpująca lista tu: Object Relational Impedance Mismatch.

/*
 * Na marginesie dodam, że c2.com jest nieprzebraną
 * kopalnią wiedzy - co prawda ascetyczną,
 * ale pełną materiałów uzupełnionych
 * wnikliwymi przemyśleniami, polecam.
 */


Żeby wymienić te bardziej istotne "niezręczności":
- Enkapsulacja - a właściwie jej brak:) Nie ma to jak przykładowa kolekcja pozycji na zamówieniu: order.getItems() wybebeszona na wierzch aby można było ją radośnie modyfikować bez wiedzy obiektu order. Czy ktoś dziś pamięta jeszcze od dobrej praktyce zwracania Iteratora? W naszym przypadku mielibyśmy order.getItemsIterator(), który pozwala jedynie bezpiecznie przejrzeć pozycje. Natomiast jakakolwiek modyfikacja musi być wykonywana przez zamówienie, np: order.addItem(Item). Co na to daje? Ano obiekt zamówienie lepiej od nas powinien wiedzieć co z tym fantem zrobić (gdzie dodać, a może coś przeliczyć):P

Dobrze, że mapery od jakiegoś czasu wspierają prywatne pola bez getterów/setterów. Można coś w ten sposób powalczyć.

- Typy danych (grafy vs krotki) - w OO mamy wskaźniki/referencje, natomiast w świecie relacyjnym iloczyny kartezjańskie wartości. Krotki jak to krotki - nie zawsze muszą mieć sens;)

Przy okazji: czy ktoś może mi podać racjonalny powód, dla którego w JPA muszę zawsze używać słowa DISTINC w zapytaniach aby uchronić się przed produktem iloczynu? Przykładowo gdy chcę pobrać listę zamówień a wraz z nimi dociągnąć pozycje to otrzymam zduplikowane obiekty zamówień - z uwagi na kartezjan z pozycjami. Pokutuje tu niestety myślenie relacyjne. Przecież wydaję zapytanie o graf obiektów a nie o jakiś zakichany iloczyn!

- Dane/odpowiedzialność - myślenie relacyjne jest nastawione na dane, obiektowe powinno być nastawione na zachowanie.




Zatem wydawać by się mogło, że sensowne mapowanie relacyjno-obiektowe wymagać musi nie lada akrobacji umysłowych. Jednak nie koniecznie:) Zamiast rozwiązać problem (np uznając, że się nie da) udajemy radośnie, że wszystko jest ok:)

Standardowe wykorzystanie ORM jest zawieszone niejako pomiędzy dwoma światami ze swymi żałosnymi namiastkami obiektów zredukowanymi do karykaturalnych paczek danych - Anemic Model.

Nie należy oczywiście popadać w paranoję i dążyć do tego, że każdy kod musi być obiektowy. Jeżeli nie ma takiej potrzeby/możliwości to pewnie, że nie. Ale po co się wówczas wydurniać z całym tym mapowaniem, które i tak jest nieudolne? Czy zamiast sztampowej klasy Person z polem firstName nie wystarczy hehe result[35]? No dobra, to przegięcie - niech będzie resultMap.get(Fileds.FIRST_NAME)? Po co się wydurniać i oszukiwać, że mamy jakieś OO.
O co walczyć w przypadkach gdy kod (encji jak i cała reszta) i tak nie jest obiektowy lecz proceduralny?
Czy nie będzie to tylko niepotrzebny narzut pasujący jak kwiatek do kożucha - że zakończę kontrowersyjnie jak obiecałem;P

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

Oprócz filozoficznych zagadnień pojawiają się również całkiem praktyczne:

- modelować począwszy od bazy czy warstwy obiektów domenowych? Ja mam swoje zdanie sprawdzone w boju: Życia nie oszukasz - jeżeli tylko jest taka możliwość zacznij od obiektów (mapery tutaj akurat robią dobrą robotę generując automagicznie bazę z klas - stosować tylko w fazie "szału tworzenia")

- W kontekście Doman Drive Design: metody biznesowe w encjach oczywiście nie są fajne, gdy chcemy przesyłać encje z/do klientów. Pytanie czy na pewno chcemy je tam przesyłać? Naiwne mapowanie (zamapowane wszystkie powiązania z bazy) pozwala dodając zamówienie dodać razem z nim gdzieś tam w niezachermetyzowanych bebechach encję nowego usera z fajnymi uprawnieniami:)
W małych systemach na pewno wygodnie jest posługiwać się encjami we wszystkich warstwach, ale w większych jednak oldskulowe DTO pozwoli ukryć strukturę modelu biznesowego.

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.