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ć):PDobrze, ż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:
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ć;
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
@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.
Prześlij komentarz