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.

3 komentarze:

arkadiusz borek pisze...

Witam;

Zacznę do pochwały, ponieważ tekst jest bogaty merytorycznie, a zarazem bardzo lekki do czytania, potem będzie kilka słów polemiki;

niedopasowania świata relacyjnego i obiektowego jest problemem, ale jest on znikomy gdy osoba zajmująca się problemem ORM ma o tym świadomość;

zdziwiło mnie bardzo zdanie: „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? „ nie wiem jak by miło wyglądać samo zapytanie pobierające informacje ale napisanie czegoś takiego w Hibernatei: „select i from Invoice i inner join fetch i.items it where i.invoiceId = ?” zawsze da nam dokładnie jedną fakturę;

jeszcze raz odwołam się do wiedzy programisty, a dokładnie do wiedzy na temat OOP gdzie on sam powinien (po nie do końca inteligentnym maperze) poprawić mapowania :P, a może jakiś interfejs na świat wypuścić;

Marek Dominiak pisze...

Witam,

@Arek chyba się jednak mylisz, Hibernate robi dokładnie to samo co JPA. Przykład wyciągnięty z tutoriala Hibernate'a:

-------------------------------
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());


...

"Note that queries that make use of eager fetching of collections usually return duplicates of the root objects (but with their collections initialized). You can filter these duplicates simply through a Set. "
------------------------------


Problem z potrzebą stosowania w JPA słowa DISTINCT w tym przypadku o którym pisze Sławek może wynikać z tego że programista mógłby chcieć w tym zapytaniu inne dane i wrzucić je np.: do hashmapy ;-)

select mother, kittens.name
from Cat as mother
left join mother.kittens kittens

W tym przypadku komuś mogłby się przydać się iloczyn kartezjański, jeśli JPA domyślnie by robiło distincta to mogłoby to być niemożliwe. Z drugiej strony implementacja JPA już sama powinna wiedzieć co wyciągamy tak więc w tym przypadku powinna pozwolić na wyciągnięcie kartezjana, a gdy wyciągamy tylko graf obiektow to powinna zwracać od razu bez powórzeń.

Dochodzę więc do wniosku że wymóg użycia słowa distinct jest bezsensowny ..

No chyba że:
- jest to powód optymalizacyjny (w co szczerze wątpie)

albo

- ma to tylko ułatwić przejście starym wygom SQL-owym na nowe pole (pseudo)obiektowe ;-)


--
Pozdrawiam

Marek Dominiak pisze...

@Arek: No tak nie zauważyłem tamtego warunku w where :-) Tak więc mimo że JPA i Hibernate działają podobnie to warunek wyklucza powtórzenia.