sobota, 29 czerwca 2013

Mapowanie relacyjno-obiektowe prawdziwych obiektów – rzecz o DDD i JPA

Czy wiesz, że:
  • Lazy Loading nie ma sensu
  • Mapowanie @OneToMany z wykorzystaniem tabeli linkującej (domyślne zachowanie hibernate) nie ma sensu
  • Blokowanie Optymistyczne oparte jedynie na @Version nie ma sensu
...jeżeli stosujesz zasady Object Orinted i JPA (lub inny maper relacyjno-obiektowy).


Jeżeli powyższe tezy targnęły Twoimi emocjami, to zapraszam do lektury najnowszego artykułu: Mapowanie relacyjno-obiektowe prawdziwych obiektów – rzecz o DDD i JPA (do pobranie całkowicie free), który ukazał się w najnowszym numerze programistamag.pl (wakacyjny numer o podwójnej objętości dostępny w Empikach lub w formie elektrycznej).

Zapraszam do śledzenia całej serii Laboratorium Bottega - Receptury projektowe – niezbędnik początkującego architekta.

17 komentarzy:

Krzysiek pisze...

"Czy wiesz, że: Lazy Loading nie ma sensu".

Stwierdzenie trochę jak z SE czy Faktu, zbyt ogólne i nie jest prawdziwe w każdym przypadku. Co jeśli mamy kompozycję, lecz obiekt A zawiera listę wielu (nawet do kilkuset) obiektów B? Nie zawsze nam lista obiektów B jest potrzebna (np w przypadku gdy chcemy wyświetlić listę nazw obiektów A).

Wojtek (szogun1987) pisze...

@Krzysiek do ładowania listy zamówień nie powinno się ładować wszystkich Agregatów zamówień jakie są widoczne na liście. Tylko klasy OrderData (a jeszcze lepiej po prostu listę hashmap ładowaną procedurą składowaną bo tak wydajnie). Zauważ że agregat Order nie zawiera referencji do Encji/Agregata klienta tylko do ClientData które jest kawałkiem informacji o kliencie, który akurat jest potrzebny w zamówieniu. Moim zdaniem referencja Do Product też powinna wskazywać na ProductData (jeżeli akurat mamy system dla zakładu produkcyjnego to Product może zawierać informacje o półproduktach potrzebnych do wykonania produktu który w kontekście zamówienia nie jest informacją szczególnie potrzebną).
Chyba się za bardzo rozpisałem :/

Krzysiek pisze...

@Wojtek, ale ja nie o tym pisałem...

Wojtek (szogun1987) pisze...

@Krzysiek jak dobrze się wczytasz to znajdziesz odpowiedź. Brzmi ona nie wyświetlamy listy obiektów A jeżeli A ma w sobie listę B. Wyświetlamy listę obiektów AData. Oczywiście nie mówimy o aplikacjach typu prosta książka adresowa, operującej na lokalnej bazie danych, gdzie sensowne jest operowanie na Klasie/Encji Firma mającej kolekcje Pracownicy/Kontakty. Tylko o systemach o niebanalnej logice biznesowej (np. wspomniana przeze mnie wcześniej obsługa zamówień połączona z zamówieniem brakujących półproduktów, wystawieniem Faktury pro forma, obsługą płatności i przygotowaniem 2KC dla prezesa po tym jak będzie świętował udaną sprzedaż).

Krzysiek pisze...

A jak pobierzesz listę obiektów AData?

Wojtek (szogun1987) pisze...

Jeszcze raz A jest kontenerem zawierającym pierdylion pól w tym listę obiektów B. AData jest dedykowanym typem mającym minimalną ilość pól odpowiadającą mniej więcej temu co widać na ekranie. Listę obiektów AData pobieramy z osobnej metody a może nawet z osobnego serwisu/bazy(np. z hurtowni danych). A służy do operacji zmieniający stan systemu np. do dodania pozycji do zamówienia, AData służy tylko do informowania o tym stanie (np. do wyświetlenia na liście).
Btw przeczytałeś artykuł?

Sławek Sobótka pisze...

@Krzysiek
Retorykę na poziomie Faktu zastosowałem ze świadomą przekorą aby nieco rozruszać "sezon ogórkowy";P Celem było zachęcenie do wgłębienia się w artykuł (jak zauważył Wojtek).

Co do "zbyt ogólne i nie jest prawdziwe w każdym przypadku" to masz jak najbardziej rację. Przykład, o którym piszesz jest w sumie oczywisty i zakładam, że każdy wiec do czego służy LL. Wojtek nawiązuje do przypadków nietrywialnych. Ja natomiast podałem bardzo konkretny kontekst, w którym zanurzyłem 3 tezy z posta - szczegóły w treści artykułu.

Irek Matysiewicz pisze...


To nie jest kwestia lazy loadingu, tylko zbyt niskiego poziomu izolacji (litera "I" z ACID, zapewne był to read committed, bo to zwykle używane domyślnie).

Twój przykład bez lazy loading zadziałał bo (w uproszczeniu) baza na czas wykonywania polecenia podnosi poziom izolacji na serializable, i dostałeś taki efekt jakby cała transakcja miała ten poziom izolacji.

Sławek Sobótka pisze...

@Irek
Tak, technicznie rzecz biorąc sęk w izolacji.
Natomiast idea jaką opisuję rozwiązuje problem na innym poziomie: jeżeli modeluję obiekt (a nie strukturę danych), który chroni swe nie niezmienniki (reguły biznesowe - technika DDD) to zakładam, że i tak prędzej czy później będę potrzebował wszystkich składowych obiektu. Z drugiej zaś strony stosując technik DDD obiekty nie są "duże" - są odpowiednie, tak aby modelować reguły.

Jakub Nabrdalik pisze...

Dobry art.
Ze względu na te bóle przesiadłem się na bazy dokumentowe.

Giker pisze...

Poprawcie mnie jeśli się mylę. Problem z lazy loadingiem i dwom wątkami jest trochę naciągany. Z tego co pamiętam lazy loading musi być wykonany w aktywnej transakcji. Obliczenie ceny zamówienia jest to czynność biznesowa i powinna odbywać się w jeden transakcji. Transakcje najczęściej są per wątek więc oba wątki działaby w osobnych transakcja. Stąd pierwszy wątek w trakcie wykonywania swojego lazy loading'u widziałby w dalszym ciągu 2 elementy. A to, że inny wątek zmienił w między czasie stan w bazie to już inna sprawa, która można rozwiązać tak jak pokazano w dalszej części artykułu.

Sławek Sobótka pisze...

@Giker
Sytuacja może mieć kilka scenariuszy:
1. kilka requestów do serwera w konwersacji z rozszerzonym kontekstem persystencji - wówczas problem występuje w oczywisty sposób
2. jeden request - wówczas to zależy:
bo czym innym jest transakcja aplikacyjna (ta, którą zarządza framework) a czym innym bazodanowa. Nawet jeżeli mówimy o jednej transakcji bazodanowej, to teraz pytania jaki jest poziom izolacji równoległych transakcji http://pl.wikipedia.org/wiki/ACID

Paweł Kaczor pisze...
Ten komentarz został usunięty przez autora.
Paweł Kaczor pisze...

EclipseLink oferuje dodatkowe adnotacje (@PrivateOwned i @OptimisticLocking), które ułatwiają zdefiniowanie integralności agregatu mapowanego na tabele w bazie relacyjnej.

http://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_privateowned.htm#CHDDDDAI

http://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_optimisticlocking.htm#BCGIACHD

Sławek Sobótka pisze...

Dzięki Paweł. Ciekawe rozszerzenie, widać świadomość problemów OO w obozie Eclipse:)

Mateusz Rasiński pisze...

Co wy na to, żeby encje JPA w ogóle traktować zupełnie osobno, gdzieś w warstwie infrastruktury? Z poziomu domeny pobieramy piękne obiekciki domenowe z repozytorium, a to implementacja repozytorium martwi się o wykorzystanie konkretnych encji. Te wszystkie sztuczki i mapowania JPA wtedy są w jednym miejscu, a logika biznesowa - w drugim.

Sławek Sobótka pisze...

W sensie aby przepakowywać encje na agregaty ddd i vice versa?
Jak dla mnie nieciekawie... po to używamy ORM aby nie babrać się w przepakowanie ResultSet w obiekty a tutaj trzeba by obiekty w obiekty. To już lepiej samemu ResultSet w agregaty.

Albo ew. Agregaty wrapuję anemiczne encje i każda metoda biznesowa woła getery i setery na wewnętrznych encjach.
Ale co nam by to dało? Czasem ludzie tak robią, bo encję są generowane z bazy podczas builda więc chronią się przez nadpisaniem kodu domenowego.