sobota, 30 maja 2009

Nie ma rzeczy niemożliwych

/*Post rozpoczynający serię: żarciki branżowe*/

http://www.getacoder.com/projects/detect_loop_106243.html
...jak widać jest wielu śmiałków gotowych od ręki i za niewielkie pieniądze rozwiązać problem stopu.
Ba... nawet licytują się, kto zrobi to szybciej i taniej.

I nie straszny im dowód nierozwiązywalności; teoria jest dla filozofów - "because we can"!

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

Tradycyjnie tu powinien pojawić się mój własny komentarz. Jednak jako, że dziś wyjątkowo post z przymrużeniem oka to tym razem oddam głos temu panu:

piątek, 29 maja 2009

Stare Chińskie przysłowie: Jest czas na pracę i jest czas na zbieranie ryżu




Podobno to Hindusi odkryli system dziesiętny - Arabowie jedynie go rozpowszechnili (mając silne argumenty w dłoniach;).
Nieco mniej chlubnym osiągnięciem indyjskiej myśli jest na przykład biblioteka tagów JSP do integracji z JPA, na którą to niedawno przypadkiem się natknąłem. Zdaje się, że przyświeca jej motto "because we can!"

Stereotyp programisty Hindusa, który "nakłada gacie przez głowę" jest dosyć mocno rozpowszechniony i zakorzeniony ale wystrzegajmy się pychy.

Ciekawe studium hinduskiej mentalności znajdziemy w interesującej, lekkiej ale bardzo pouczającej książce Moja Praca Emigruje do Indii - A wszystko, co dostałem, to ta marna książka. Ale to tylko jeden z aspektów. Poznamy przede wszystkim globalne mechanizmy korporacyjne mające wpływ na niemal każdego z branży IT.

Chad Fowler zabiera nas w egotyczną podróż do Bangladuru, gdzie spędził 1.5 roku tworząc Software House dla jednej z amerykańskich korporacji.

Poznamy szereg kuriozów:
- na ogłoszenie o pracę odpowiada średnio kilkanaście tysięcy chętnych
- a mimo niemałego budżetu nie można sobie pozwolić na tych doświadczonych
- taksówkarz zna 5 języków
- a programista nie ma własnego komputera w domu

Każdy z 52 zgrabnych i wyważonych rozdziałów niesie w sobie pewien morał. Lokalny folklor jest jedynie pretekstem do przekazania metafory lub przesłania. Dostajemy zestaw gotowych strategii, uwag, ostrzeżeń i porad z zakresu szeroko pojętego samorozwoju i autopromocji. Kto się nie rozwija ten się cofa a zmiany są nieuniknione.

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

Wymowa książki w naszym lokalnym kontekście nabiera jednak drugiego dna. Nie musimy obawiać się emigracji naszej pracy do Indii. Być może jesteśmy Indiami Europy:P

sobota, 23 maja 2009

Understandability

Understandability - ostatnio modny termin. Znak, że oto kolejne pokolenie programistów/developerów/inżynierów oprogramowania (byle nie "informatyków"!) dorosło do przepoczwarzenia. Z kał-bojów i szambe-lanów babrzących się w cudzych (tudzież własnych) klasach z archiwum X chcieliby stać się szczęśliwymi profesjonalistami wiodącymi szczęśliwe życie wśród łatwych, prostych i przyjemnych linijek kodu.


Komentarze

#define TRUE FALSE
//Happy debugging suckers

Stara szkoła mówi aby 70% tekstu stanowiły komentarze. Tak aby po hipotetycznym usunięciu kodu można było go łatwo odtworzyć na podstawie ich treści.
W praktyce oczywiście nie ma czasu na twórczość o tej skali - zarówno podczas tworzenia jak i utrzymywania kodu. Poza tym wydaje mi się, że takie podejście zmienia niewiele:
- pisanie dobrych komentarzy jest sztuką jeszcze większą niż pisanie dobrego kodu (o czym dalej) - więc wiadomo czego możemy się spodziewać
- komentarz daje nam jedynie bardzo lokalny obraz sytuacji, aby uchwycić tak zwany biger pikczer potrzeba zdecydowanie czegoś więcej...

Ileż to razy bierzemy jakąś bibliotekę/framework (załóżmy, że rzetelnie okomentowane) i niestety nie możemy połapać się w gąszczu dziesiątek/setek klas. Przydałby się jakiś wizualny standard prezentowania ogólnej architektury konceptu. Pomocny byłby już choćby jakiś sposób uwypuklenia zastosowanych wzorców projektowych/architektonicznych.

Owszem jakimś rozwiązaniem jest narzędzie do reverse engineering, przy pomocy którego możemy sobie wygenerować UML. Ale co mi po gąszczu prostokątów jeżeli nie wiem, które są istotne, które stanowią core konceptu, a które jedynie szczegóły impl.


Kod samokomentujący się
Skoro i tak muszę pracować z kodem to fajnie byłoby gdyby był intuicyjny. Niedoścignionym wzorem na tym polu jest:
Epopeja poświęcona poziomowi inteligencji dzielnego Ryśka - spisana w tej oto abstrakcyjnej klasie RichardIsAFuckingIdiotControl.

To mój styl - nauczyłem się go analizując źródła Springa. Nie boję się opisowych nazewników klas czy metod mających po 30, nawet 50 znaków. To nie czasy dyskietek, gdzie od ilości literek zależało czy musimy żonglować nośnikami aby skompilować projekt.

Oczywiście gdy z sygnatury metody nie mogę wywnioskować co ona robi to nie powinienem zaglądać do środka - komentarz jest jak najbardziej wymagany.

W tym przypadku, podobnie jak w poprzednim również mamy problem z ogarnięciem ogólnej idei rozwiązania. Jednak niektórzy porządni rzemieślnicy zostawiają czasem wskazówki w nazwach - przykładowo przyrostki wskazujące na zastosowany wzorzec: xxxCommand, xxxStrategy, itp.


Zbiorowa inteligencja
Niektórzy postulują jakoby dobry kod to taki, gdzie czytamy to czego właśnie w tym miejscu się spodziewamy. W sumie trudno się nie zgodzić - pycha pomysł. Niestety utopijny ponieważ każdy programista jest inny. Każdy ma swój styl (albo i nie - o tym na koniec) i smak. Nie zapominajmy, że zawodnicy w teamie mają różne poziomy skillsów.

Oczywiście w różnorodności siła, ale świadczy to też o braku profesji w naszym fachu, braku elementarnych zasad rzemiosła - jesteśmy bandą indywidualistów (ale to temat na następnego posta).


Czas to pieniądz
Dobra metoda, to taka gdzie zrozumienie co i jak ona robi zajmuje nie więcej niż 5 minut - to jedno z częściej powtarzanych haseł. Miara równie rozmyta jak poprzednia. To co będzie oczywiste dla kodera turbo-ninja z silnym autyzmem, który w jednej półkuli kompiluje kod do rozkazów maszynowych nie będzie już takie dla studenta zarabiającego na piwko w PHP.


Z trochę innej strony...
Nierzadko spotkam się z sytuacją, w której programista nie wie jak nazwać klasę, metodę czy instancję. Albo nazwy pakietów - czy nie powinny wskazywać na "uporządkowanie myśli" i odzwierciedlać architekturę aplikacji? Niby szczegół prawda? Ot po prostu nie mam dziś weny. Zresztą co to za różnica? Kompilatorowi przecież jest wszytko jedno. Nazwę sobie Manager5, doStuff(String param1, String param2) albo x7.

Jest to jednak niepokojący syndrom - i mam tu na myśli coś gorszego niż rak mózgu;)

Ale żeby nie było, że sobie coś ubzdurałem, podeprę się najpierw autorytetem paru guru z naszej branży aby nie stać na grząskim gruncie własnych urojeń.

W 2006 r. niejaki Jarosław Rzeszótko znany również pod zabarwionym perwersją pseudonimem artystycznym "sztywny" wpadł na ciekawy pomysł. Zadał 9 (w większości) znamienitym programistom 10 pytań: Stiff asks, great programmers answer.

Ciekawych odpowiedzi udzielili panowie na pytanie:
"What do you think is the most important skill every programmer should posses?"
Istotne okazują się zdolności "miękkie", min: komunikacyjne, pisarskie, muzyczne.
Ciekawą - bo niemal ocierającą się o neuropsychologię;) - odpowiedź dał Dave Thomas:
I _have_ seen a strong correlation between people who have some music in their background and programming skills. I have no idea why, but I suspect that some of the areas of the brain that make someone musical also make them good at software development.

Linus Torvalds dla odmiany stawia na poczucie "smaku".


Do czego porównać kod, który będzie samoopisujący się, którego zrozumienie nie wymaga zbyt wiele czasu - ba, wręczy czytamy to czego się spodziewamy?
Dla mnie taki kod jest jak powieść. Kreujemy aktorów (klasy) o pewnych cechach osobowości (metody). Niektórzy są dwulicowi (interfejsy). Budujemy scenografię (warstwy, moduły) gdzie będzie rozgrywać się akcja. Nie jakieś śmietnisko, ale dobrze zorganizowany kompleks spójnych budowli. Dalej nie pozostaje już nic innego jak opowiedzenie ich historii, począwszy od stworzenia poprzez ew. reinkarnacje;) Bez zbędnych dygresji i wnikania w poboczne wątki - te nich dzieją się we wszechświatach równoległych dostępnych przez portale mangerów zdarzeń.

Werbalizowanie historii może wydawać się bzdurne, ale zastanówmy się co nam daje. Aby np zbudować metaforę musimy włożyć pewien wysiłek umysłowy. Wypowiedzi w języku naturalnym są obłożone szeregiem ograniczeń - szczególnie ważne są ograniczenia logiczne. Dzięki nim możemy wychwycić luki w naszym rozumowaniu, pokraczne modele, cieknące abstrakcje. Ograniczenia werbalne pomagają w ogarnięciu chaosu. Można je nazwać nieformalnymi regułami walidacji poprawności koncepcji.


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

Ktoś kiedyś napisał, że programistów można podzielić na 3 grupy. Tych, który mają dobry styl, tych który mają zły styl i tych, którzy w ogóle stylu nie mają.
Ci drudzy nie są jeszcze straceni. Co prawda ich poczucie stylu jest złe, ale przynajmniej jakieś jest - przy pewnym wysiłku można je zmienić na dobre. Najgorsi są Ci, którzy w ogóle nie rozpatrują czegoś takiego jak styl - jest to poza ich zakresem pojmowania. Ci są straceni.

czwartek, 14 maja 2009

Risercz: DDD

W związku z natłokiem spraw nie mam czasu aby napisać coś bardziej sensownego na blogasku. Właściwie to problem nie tyle z samym czasem co z mózgiem obciążonym zbyt wieloma wątkami, co w rezultacie skutkuje zanikiem weny - czyli impotencją intelektualną (wszyscy pewnie znacie to uczucie nieprzyjemnego napięcia, które odbiera kreatywność).

Aby jednak podtrzymać aktywność ucieknę się do prastarego triku i wkleję trochę linków, które ostatnio sobie przyswoiłem, i które to uważam za godne zauważenia.

Będzie to jednocześnie pierwszy z nowej serii postów - raporty/streszczenia z riserczu.



Dziś będzie o (niespodzianka) Domain Driven Design. Dwa artykuły opisujące mniej lub bardziej techniczne aspekty DDD. Tak, wiem, że DDD to filozofia i technikalia nie są ważne, ale ktoś kiedyś jednak będzie musiał wziąć się do roboty i zaimplementować nasze prostokąty i strzałeczki;P


Na początek ciekawe kompendium technologiczne na temat domain driven DEVELOPMENT:
Domain Driven Design and Development In Practice.
wszyscy narzekają, że owszem DDD fajne jest, ale jak tak na prawdę podejść do projektu? W treści znajdziemy wiele ciekawych pomysłów i alternatywnych koncepcji.
Powyższy link jest co prawda Made in India, ale mimo tego jego treść jest rzetelna - poza tym marka infoq do czegoś zobowiązuje.


Dla odmiany teraz coś Made in Germany. Niestety odmiana ma charakter przede wszystkim jakościowy:/
Domain-driven design with Java EE 6
Oto jak kończy się aplikowanie prymitywnej platformy do zaawansowanej koncepcji - naiwna implementacja.

Pomysł bram (gate) może i ciekawy... Na pewno wygodny z punktu widzenia programisty. Mamy oto coś na kształt stanowego servisu, który ma w sobie entity managera w trybie rozszerzonym. Dzięki temu możemy sobie korzystać z lazy lodingu podczas ping-ponga pomiędzy klientem a serverem. Prawie jak konwersacje w seam:)
I czy w ogóle jest o co walczyć? Ile czasu zaoszczędzę posługując się Lazy Loadingiem zamiast napisać sobie Repo, które będzie używane przez agregat w celu pobrania składników tegoż agregatu? A co z wydajnością Lazy Loadingu:P
Podsumowując: życzę powodzenia przy nieco większej ilości użytkowników hehe

Na uwagę zasługuje jednak nowy termin, który próbuje się ukuć "persistent domain object (PDO)" - podoba mnie się to:)
PDO czyli Agregat w sensie DDD (posiada stan i odpowiedzialność - metody biznesowe), który dodatkowo ubabrany jest adnotacjami JPA. Czyli wreszcie dojrzewamy aby odejść od proceduralnego rozdziału struktry danych od algorytmów (encje/servisy).
Niestety taki agregat przesłany do warstwy UI ciągnie za sobą metody biznesowe, które nie powinny być dostępne w tej warstwie.
Jednym z rozwiązań jest stworzenie 2 stosów warstw - opisane na końcu tego nudnego posta, innym są mixiny z frameworki qi4j.

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

Na zakończenie ciekawa refleksja: http://www.infoq.com/interviews/domain-driven-design-eric-evans#view_10448. Nasze community nauczyło się wreszcie korzystać z podstawowych technik (które podobno istniały juz w latach 80 w Smalltalk) i wreszcie ukonstytuowały się pewne standardy z zakresu projektowania i architektury.

W skrócie: community java powoli kończy etap onanizmu technicznego i można zacząć skupiać się na problemach na prawdę istotnych.

Natomiast z komentarza tego znamienitego dinozaura http://www.infoq.com/interviews/domain-driven-design-eric-evans#view_10653 wynika, że już prawie dotarliśmy tam, gdzie community Smalltalk było w latach '80...

poniedziałek, 20 kwietnia 2009

Lazy Loading a sprawa wydajności




Podążając za ciągiem skojarzeń: od przedwczorajszego DAO przez wczorajsze Lazy Loading dziś czas na aspekt wydajności podczas korzystania z maperów relacyjno-obiektowych. Właściwie to główne niebezpieczeństwo tkwi w tytułowym Lazy Loadingu.

Sprawa jest w sumie prosta - trzeba uważać co się robi i mniej więcej orientować się jak działa mapper. Nie trzeba wnikać nawet w aspekty implementacyjne Lazy Loadingu: klasy pośredników, które mogą być podstawiane do encji czy to poprzez manipulację byte codem czy w czasie generowania kodu źródłowego (jednak zainteresowanym polecam zgłębienie tematu - warto wiedzieć "jak oni to zrobili").

Niestety na żadnej ze stron domowych maperów zgodnych z JPA (Hibernate, OpenJPA, Kodo, TopLink) nie dopatrzymy się ani na głównej stronie, ani na żadnej innej wielkiego czerwonego napisu "UWAGA NA WYDAJNOŚĆ PODCZAS STOSOWANIA LAZY LOADING!". Nie uraczymy również zestawu paru prostych porad, które uchroniły by nas przed dramatycznym spadkiem wydajności. Czyżby nie wyglądało to zbyt marketingowo? Może autorzy sądzą, że ludzie tego nie zauważą i jakoś to będzie - kolejny bubel się przyjmie w społeczności:)

Problem wydajności jest stary jak Hibernate ale niestety jeszcze nie wszędzie uświadomiony. Jako przykład podam tę oto przypowieść.



Niezbyt_dawno, niezbyt_dawno temu w pewnej dużej organizacji strasznie mulił system wykorzystujący Hibernate. Ludzie ze swą wrodzoną skłonnością do tworzenia mitów szybko poradzili sobie z dysonansem poznawczym trapiącym ich mózgi: stworzyli sobie prosty system mitologiczny, który nakazywał im wierzyć, że ORMy tak po prostu mają. Po prostu ich magiczne właściwości powodują, że systemy spowalniają a odczyt danych z bazy jest hamowany jakąś niepojętą siłą. Ciemnotę dodatkowo potęgowała Kasta Adminów Baz Danych, mówiąc: "tak oto kończy się sprzeniewierzanie prastarej składni SQL. Nie chciało się wam niewierni pisać procedur i kwerend to teraz sczeźnijcie w piekle!".


Po wstępnych oględzinach okazało się, że w większości przypadków obarczonych bardzo niską wydajnością mamy do czynienia z klasycznym problemem występującym w ORMach z Lazy Loadingiem: n+1 Select Problem.

Istotę problemy wyjaśnię na przykładzie: Wyobraźmy sobie, że pobieramy listę obiektów klasy Person. Następnie iterujemy po tej liście i dla każdej osoby wołamy getAddresses() po to aby coś tam z adresami każdej z osób zrobić. Jeżeli zapytanie wygląda mniej więcej tak: SELECT p FROM Person p to mam rzeczony problem. Najpierw silnik persystencji wysyła do bazy jedno zapytanie w celu pobrania listy osób. Następnie dla każdej z n osób wysyła zapytanie o jej adresy. W sumie n+1 zapytań:) Pięknie nieprawdaż? Niestety musimy pofatygować się i napisać porządne zapytanie HQL (lub odpowiednie Criteria w Hibernate), które spowoduje dodżojnowanie adresów do osób tak aby wysłane zostało do bazy jedno zapytanie SQL. Niestety nieodzowne będzie dopisanie w HQL paru JOIN FETCH - a co za tym idzie korzystanie z Entity Managera przestaje być takie trywialne jak w tutorialach, pojawia się złożoność, którą warto hermetyzować w DAO/Repozytorium.

Należy uważać również na "utajony" wariant n+1 select problem. W powyższym przykładzie mamy sytuację, w której radośnie iterujemy sobie przy pomocy własnego kodu po osobach i grzebiemy w ich adresach. Może być też tak, że nasz własny kod jest OK, ale niefortunnie podepniemy naszą listę osób pod np jakiś komponent GUI (np h:dataTable w JSF), który ma wyświetlić osoby wraz z adresami i nieszczęście gotowe. Logika renderingu tabelki przeiteruje po osobach aby wyrenderować jej adresy. Wówczas mogą zdarzyć się 2 rzeczy:
- aplikacja się wywali z powodu LazyInitializastionException (lub jakiegoś innego wyjątku w zależności od dostawcy JPA - co zresztą uważam za skandaliczne) ponieważ najpewniej w warstwie widoku sesja będzie już dawno zamknięta
- jeżeli jednak stosujemy jakiś trik typu Open Session In View to wówczas ORM radośnie wygeneruje nam n+1 zapytań do bazy:)))

Pierwszy wariant (wyjątek) jest o tyle dobry, że przynajmniej zwróci czyjąś uwagę podczas devlopmentu i skłoni do napisania porządnego zapytania, które ładuje wszystkie dane w jednym podejściu. Drugi wariant działa więc jest szansa, że nitk nie sprawdzi co się dzieje po stronie bazy:/



Kolejna przypowieść. W tejże samej organizacji istniał inny system, który również niemiłosiernie mulił. Tym razem był to gruby klient do serwera więc lazy loading nie miał zastosowania. Encje przesłane na klienta nijak nie chcą nawiązać kwantowego kanału z procesorem serwera:/ W tym systemie również panowała mitologia. Kiedyś pewien magik podobno zrobił czary mary i LazyInitalizationException nie pojawiał się gdy na kliencie wołano łańcuszki get get get.


Po krótkiej analizie problemu okazało się, że tym razem przedobrzono w inną stronę. Prawie wszystkie powiązania pomiędzy obiektami były EAGER - czyli chciwe lub jak kto woli łapczywe. Skutkowało wyciąganiem połowy bazy przy każdym zapytaniu. O ile serwer bazodanowy dawał radę to wąskim gardłem okazała się transmisja zserializowanych danych. Wniosek znowu ten sam: niestety na skróty się nie da - trzeba się pofatygować i napisać porządne zapytania specyfikując co w jakim Use Case ma być podciągnięte. Niestety zakorzeniona mitologia i szargana opinia o ORMach pozostanie w organizacji na parę następnych pokoleń.

Przy okazji warto wspomnieć o kolejnej pułapce jaką szykuje na nas maper. Uwaga na powiązania typu n-1 lub 1-1 - czyli takie gdzie w klasie encji mamy pole klasy innej encji (np Person ma jeden Adres). Standard JPA zakłada, że wówczas powiązanie jest typu EAGER. Czyli pobierając z bazy osobę zawsze od razu wyciągniemy jej adres. Niestety odbywa się to zazwyczaj w osobnym zapytaniu. Czyli w przypadku pobierania listy osób mamy n+1 Select Problem:)


Żeby nie było, Lazy Loading może również przydać się w celu optymalizacji systemu.
Wyobraźmy sobie taki przypadek: Mamy klasę dokument, która zawiera szereg pół - w tym pole zawierając treść dokumentu: kilkadziesiąt stron A4 (pomińmy kwestię sensowności takiego projektu). Gdy chcemy zaprezentować listę dokumentów na GUI, która zawiera dajmy na to tytuł i datę to oczywiście zupełnie niepotrzebnie będziemy pobierać z bazy i przechowywać w aplikacji (choćby chwilowo) dziesiątki megabajtów tekstu. Jednym z paru rozwiązań ale najprostszym jest ustawienie leniwego ładowania kolumny z treścią. Gdy nie będzie potrzebne wówczas nie będzie ładowane z bazy.


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

Podstawą pracy z ORM jest konsola z logami. Podczas developmentu należy włączyć wyświetlanie generowanych poleceń SQL i nieustannie obserwować oczami swymi logi silnika ORM czy przypadkiem nie pojawia się na niej w pewnym momencie kilka/-dziesiąt/-set/-tysięcy zapytań. Oczywiście n+1 Select Problem możemy zauważyć na konsoli praktycznie tylko wówczas gdy napełnimy bazę większą ilością danych. Dla n=1 problemu raczej nie uraczymy.

W dobrym procesie produkcji softu jest czas na takie coś jak kontrola kodu - przeglądanie nowego kodu przez bardziej doświadczonych członków teamu (lub chociaż przez drugą parę oczu). Co powiecie na kontrolę konsoli? Pokaż misiu jakie zapytania do bazy generuje Twój kod.

Podsumowując dwie przytoczone przypowieści pozwolę sobie zacytować Franka Zappa: "Głupota ma pewien urok, ignorancja nie".

niedziela, 19 kwietnia 2009

DAO a sprawa Lazy Loading



Wracając do wczorajszego tematu DAO rozwinę nieco kontekst mapowania relacyjno-obiektowego na przykładzie standardu JPA. Dokładnie - skupię się na głównym ficzerze każdego mapera zgodnego z JPA, czyli tytułowym leniwym (opóźnionym) ładowaniem zagregowanych w encji obiektów.

Niektórzy pewnie zastanawiają się czego znowu ten zgred się czepia. Przecież LL to taki fajny ficzer. Mamy sobie załadowany z bazy obiekt (i jeżeli sesja kontekstu perzystencji nie została zamknięta) to wystarczy zawołać na nim getXXX(), czary mary i pobrany przez getter zagregowany obiekt został podciągnięty z bazy oraz zapakowany do encji.

Wszystko fajnie, ale jeżeli korzysta się z tego z świadomie i w odpowiednim momencie. W przeciwnym wypadku mamy spore problemy z wydajnością. Ale o tym za w następnym poście. Ja mam z tym jeszcze pewien problem natury filozoficznej, który pojawia się gdy do naszej architektury wprowadzimy warstwę DAO (bez której jak bez ręki - co chciałem wczoraj wykazać)...

Zatem weźmy przykładowy interfejs DAO:


public interface PersonDAO{
Person getPerson(Long id);
}


Standard... zwykłe DAO (nie czas teraz na Repozytorium z DDD), zwykły klucz (nie czas teraz na UID czy klasę kluczą w dobrym stylu DDD).

Dalej będzie oczywiście jedna z możliwych implementacji w JPA:


public class JpaPersonDAO implements PersonDAO{
EntityManager em;
Person getPerson(Long id){
return (Person)em.find(Person.class, id);
}
...
}


Też standard, bez żadnego generic DAO czy innych klas bazowych, bez wnikania w szczegóły wstrzykiwania EM. Nie w tym rzecz, chodzi tak jak wspomniałem o problem natury filozoficznej, mianowicie:
Co ja mam napisać w komentarzu metody getPerson() w interfejsie? (i od razy mówię, że banał typu "return returns person that has ID relevant do given id" mnie nie zadowala)

Aby w komentarzu napisać prawdę, całą prawdę i tylko prawdę musiałbym popełnić coś takiego (po polsku będę komentarz pisał - wybaczcie):

"
Metoda zwraca osobę o zadanym ID. Drogi programisto-użytkowniku klas implmentujących ten interfejs, wiedz ,że niniejszy interfejs zawirea z tobą kontrakt o następujących warunkach:
1. O ile korzystasz z implementacji DAO opartej o JPA to możesz się spodziewać tego, że:

1.a Niektóre klasy obiektów zagregowanych w zwróconej instancji osoby zostaną zastąpione przez klasy specyficznych pośredników (np PersistentBag w hibernate) więc niniejszy interfejs nie może zagwarantować Ci poprawnej pracy w środowisku rozproszonym (np aby wynik działa tej metody przesłać na zdalnego klienta musisz mu zapewnić odpowiednie biblioteki w odpowiednich wersjach).

1.b Być może masz dostęp do obiektów zagregowanych w zwróconej instancji. Na przykład do adresu (przez metodę getAddress). Zależy to od tego czy znajdujesz się w miejscu, w którym jeszcze obowiązuje sesja kontekstu persystencji. Jeżeli sesja będzie zamknięta, a ty będziesz brzydko się bawił z instancją person to dostaniesz np LazyInitializationException - ale tego w tym momencie nie mogę zagwarantować, gdyż zależy to od dostawcy (specyfiakcja JPA nic nie narzuca w temacie reagowania na zamkniętą sesję). Niestety niniejszy interfejs jest czystą abstrakcją dostępu do danych i nie mogę Ci zdradzić szczegółu na jakim dostawcy JAP będzie oparta implementacja tego interfejsu.

2. Jeżeli implementacja tego interfejsu nie będzie oparta o JPA (a np o dostęp do web service albo pliku XML) to wiedz, że dostępu do żadnych zagregowanych obiektów nie masz.

return osoba o zadanym ID, z którą ni cholery nie wiadomo co można na 100% bezpiecznie zrobić.
"


Ogólnie chodzi mi o to, że staroświeckie DAO i nowoczesny mechanizm Lazy Loadingu mają się nijak do siebie.

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

Mamy tu do czynienia z bardziej ogólnym problemem: przeciekającą abstrakcją. Szczegóły techniczne mappera relacyjno-obiektowego (takie jak lazy loading, problem z wygasłą sesją) literalnie wyciekają z interfejsu DAO, który powinien być czystą abstrakcją dostępu do danych niezależną od jakiś maperów. Innymi słowy klient (kod) interfejsu DAO musi być niestety świadomy pewnych "wyciekających" mechanizmów stojących za jedną z jego implementacji. Nie może jej traktować jako czarnej skrzynki, która robi to co ma robić - i robi to dobrze.

sobota, 18 kwietnia 2009

DAO



Jak donoszą brukowce DAO odeszło i zostało zastąpione przez JPA. Czy aby na pewno?

Piotrek w swym poście (będącym niezłym zbiorem argumentów w dyskusji) wyraził zdziwienie pojawiającymi się od pewnego czasu głosami przeciwko DAO. Natomiast ja chciałbym dodać od siebie dodatkowo nieco typowej dla siebie irytacji spowodowanej wąskim spojrzeniem na problem pojawiającym się w głosach autorytetów z wielkiego świata.

Problem potrzeby lub nie warstwy DAO nie jest oczywisty i zależy od kontekstu. Czasem DAO jest niezastąpione a czasem może lekko przeszkadzać. Ale na pewno pozbawione kontekstu autorytarne głosy typu "DAO bad" są bez sensu.


DAO ma na celu separację logiki dostępu do danych od logiki biznesowej/aplikacji.
Co nam to daje?
Szeroką i sensowną listę podał Mario Gleichmann w swoim poście.

Ja ograniczę się do paru z nich:

  • wynikającą z separacji możliwość podmiany sposobu dostępu do danych
  • wynikający z możliwości podmiany zwiększony poziom testability
  • reużywalność logiki dostępu do danych


Przyjrzyjmy się zasadności wylistowanych argumentów:
Podmiana sposobu dostępu do danych - teoretycznie silny argument; praktycznie bezużyteczny. Jak często zdarzyła się Wam taka sytuacja? O ile bardzo często zmienią się (no w zależności od klienta) wariacje logiki biznesowej to zmiana źródła danych? O ile zmienną logikę biznesową warto hermetyzować pod stabilnym interfejsem i jej wariacje enkapsulować we wzorcu strategii to robienie tego z dostępem do danych wydaje się nakładaniem gaci przez głowię. Chyba, że...

Testowanie - separacja dostępu do danych od logiki biznesowej zwiększa dramatycznie poziom testability, czyli podatność kodu na testy. Tak podatność na testy - ponieważ nie każdy kod można łatwo testować.
Jeżeli mam osobną klasę realizującą jakąś funkcjonalność biznesową oraz osobną klasę DAO odczytującą/modyfikującą dane to wówczas:
- możemy poddać testom sam "komponent" DAO - co się oczywiście będzie rzadko zdarzać, więc ten argument się nie liczy;P
- możemy testować samą logikę biznesową - na czas testów nasza logika może używać innej implementacji DAO (np takiej, która nie korzysta z bazy danych). Co na to daje?
Testy logiki biznesowej są szybsze ponieważ nie tracimy czasu na operacje zapisu odczytu danych (których poprawność w tym momencie nas nie interesuje). Czas zapisu/odczytu potencjalnie może być stosunkowo długi, wystarczy wyobrazić sobie źródło danych, które jest bazą zawierającą miliony rekordów, gigantycznym plikiem, czujnikiem chemicznym, web servisem wykonującym się średnio 5 min. Zamiast 100000 testów na godzinę, możemy ich wykonać tylko 10 gdy będziemy ciągnąć za sobą bagaż dostępu do danych.

Reużywalność - posługiwanie się wprost EntityManagerem w kodzie biznesowym do pobierania encji (tak jak chcą tego piewcy śmierci DAO) jest pomysłem arcy-chromym.
Kod w stylu
em.find(Person.class, id)
nie wygląda groźnie - nieprawdaż? Niestety jest on jednak typowy dla tandetnych turoriali na poziomie Hello World. W rzeczywistości jednak piszemy kwerendę ponieważ:
- mamy dynamiczne zapytanie (np doklejamy WHERE person.name = 'xxx' o ile user wpisał imie, itp)
- chcemy dociągnąć chciwie zagregowane obiekty (i nie życzymy sobie lazy loadingu gdyż nierzadko JEST ZŁY - o czym będzie w następnym poście)

Nawet jeżeli w pierwszej iteracji nasze pobranie danych jest prostym find() to zwykle za miesiąc zmieni się w kwerendę. Nie chcemy zatem programować metodą Kopiego-Pasty. Dzięki istnieniu DAO możemy reużywać złożoną logikę zapytań oraz oczywiście zmieniać je w 1 miejscu.

Separacja dla mnie osobiście separacja kodu - choćby zapytania były trywialne albo totalnie niereużywalne jest wystarczającą zaletą przemawiającą za DAO.


Potrafię sobie wyobrazić, że żadne z powyższych uwarunkowań nie występują. Tzn:
- nigdy nie zmieniamy źródła danych - co jest naturalne
- nie testujemy kodu - nie oszukujmy się, taka jest rzeczywistość
- nie mamy złożonych zapytań i logiki sklejania HQLa

Wówczas możemy radośnie używać EntityManagera w kodzie biznesowym. Sugerowałbym jednak kompromis: wystarczą osobne klasy realizujące logikę dostępu do danych - bez abstrakcji: bez interfejsu, bez wstrzykiwania.



Wąski kontekst
W bardzo prostych aplikacjach trzymanie się ściśle architektury z warstwą DAO niesie ze sobą pewnie narzuty i utratę prostoty. Mam tu na myśli niezbyt ambitne ale bardzo potrzebne na rynku i popularne aplikacje typu przeglądarka bazy danych. Po prostu łatwiej i taniej jest wyprodukować za 1 mln zł system, który w przeglądarce wyświetli tabelkę z listą kalesonów w hurtowni niż postawić samą bazę danych i przyuczyć pracowników z podstaw SQL;)))

W tego typu systemach zwykle corowa funkcjonalność to ekrany z różnego rodzaju listami + kontrolki do wpisywanie kryteriów + button SZUKAJ. Do tego ekran podglądu i edycji.

Najbardziej produktywne jest wówczas podejście minimalistyczne. Warstwa prezentacji tworzy obiekt Criteria i przesyła go niżej do wykonania. Przekombinowane podejście polegające na tworzenie warstwy DAO, która abstrahuje źródło danych zmusza nas również do abstrahowania od kryteriów wyszukiwania. GUI musi wysyłać jakieś DTO, które w DAO jest przepakowywane w Criteria. Generalnie: nakładanie gaci przez głowę.

Szerszy kontekst
Dla kontrastu rozważmy hipotetyczny przykład skomplikowanego dostępu do danych.
Wyobraźmy sobie, że w naszym systemie mamy użytkownika przechowywanego w 1 bazie danych. Z niewiadomych przyczyn jego zamówienia są przechowywane w innej bazie. Adresy tegoż użytkownika są pobierane z zewnętrznego systemu przez jakiś WebService.
Załóżmy też, że w zależności od wdrożenia druga baza danych (ta z zamówieniami) oraz źródło adresów są zmienne.

Podążając za DDD stworzylibyśmy agregat User oraz repozytorium UserRepository, które jest w stanie go zebrać i poskładać do kupy. Repozytorium mogłoby używać EntityManagera do pobrania encji User oraz dwóch DAO do pobrania zamówień oraz adresów z abstrakcyjnych źródeł. Problem dostępu do danych może zawierać w sobie również aspekt przepakowania obiektów z jednej domeny (np z innego systemu) do domeny innej.


Przykład powyższy może wydawać się nieco wydumany i przesadzony, ale w rzeczywistości tego typu spawy istnieją i nie należy o tym zapominać. Chciałem tylko pokazać, że architektura dostępu do danych wcale nie jest trywialnym problemem jak przedstawia się to w niektórych materiałach lub (o zgrozo!) przez niektórych członków grup eksperckich JEE. Owszem często taki jest. Ale spektrum problemów jest szerokie i po specyfikacji platformy korporacyjnej spodziewalibyśmy się czegoś więcej.

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

Na zakończenie pamiętajmy aby dobierać sobie młotek do problemu, a architekturę dostępu do danych do... też do problemu:)

Sentencja Alberta Einsteina "Wszystko powinno być tak proste, jak to tylko możliwe, ale nie prostsze" może wydawać się trywialna, ale często o niej zapominamy.

A do implementacji większości systemów i tak nie potrzeba żadnego JPA - wystarczy Excel + jakieś cwane makro... no może Access ostatecznie;P