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
3 komentarze:
Dzięki za rozwinięcie tematu.
Sugerowany przez Ciebie kompromis uwazam za niezle rozwiazanie.
Zastanawiam sie jednak nad tym jakie dokladnie jest stanowisko pana Adama na temat DAO. Moze nas tylko podpuszcza :) Tym bardziej ze wzgledu na wypowiedz: "I much prefer the Repository (as described in the Domain Driven Design) to the DAO pattern which focus a lot more on the semantic aspect instead of the techical ones and show why the repository abstraction is important."
Moze na GeeCON uda sie to ustalic.
Bardzo konkretnie podsumowane i przedstawione - pozwolę się jednak w kilku punktach nie zgodzić.
1) Testowanie - oczywiście przeprowadzanie testów na bazie "produkcyjnej" niekoniecznie jest dobrym pomysłem. Mimo to w dalszym ciągu bardzo często może i powinna to być baza SQL (po to przecież mamy JPA aby mieć SQL). Jeżeli WIELKIE I POWAŻNE BAZY zdają się być za "ciężkie". (piszesz: "(...)na czas testów nasza logika może używać innej implementacji DAO (np takiej, która nie korzysta z bazy danych)(...)") - przecież łatwo możemy użyć jakiejś bazy "in memory" (http://en.wikipedia.org/wiki/In-memory_database) - podmianka ta będzie miała miejsce na poziomie konfiguracji, a nie na poziomie dao (którego przecież nie mamy ;-) ).
2. Reużywalność
piszesz: "(...)Dzięki istnieniu DAO możemy reużywać złożoną logikę zapytań oraz oczywiście zmieniać je w 1 miejscu. (...)" Nie możemy jednak zapominać, że w JPA mamy NamedQuery, które oczywiście są parametryzowane. Tak to się raczej powinno robić i naturalnym miejscem umieszczenia takich NamedQuery jest Encja JPA. Mieszanie kodu *QL'owego z kodem "logiki biznesowej" jakoś uderza w moje poczucie estetyki.
Niemniej zdarzyć się może, że konkretne złożone i mocno wydumane zapytanie *QL nie pasuje do żadnej z Encji, gdyż dotyczy wielu bytów i złożonych relacji między nimi - wtedy rzecz jasna coś na kształt DAO pasuje jak ulał.
Nie podważam argumentacji zawartej w podtytule "Szerszy kontekst" - jeżeli sam dostęp jest tak złożony jak w opisanym przypadku - jasne, trzeba go superspecjalnie oprogramować - to nie podlega dyskusji. Jednak nie oszukujmy się nie jest to aż tak często.
Co do pochówku DAO powiedziałbym więc raczej w ten sposób: "śmierć starym 'głupim' DAO" stosowanym w każdej sytuacji wyłącznie ze względu na niedoskonałości danego 'frameworka' - niech jednak żyją 'tworzone celowo i świadomie' nowe 'mądre' DAO ku chwale i radości ich twórców!"
Ot to takie moje 3 grosze. Pozdrawiam
Co do testów to jednak idea testów jednostkowych jest taka aby odseparować testowany kawałek i zając się nim "w warunkach laboratoryjnych". Dzięki temu wiesz, że gdy masz błąd to na pewno w testowanej klasie a nie przypadkiem w jej zależnych komponentach (oczywiście przy założeniu, że mock/fake/stub DAO wstrzyknięte na czas testów jest na tyle trywialne, że bezbłędne)
Natomiast w przypadku testów integracyjnych - gdzie testujemy większą całość - baza (np jakiś hypersonic) może być.
Oczywiście w rzeczywistości często jest tak, że jedyne testy jakie mamy to właśnie integracyjne;P
Odnośnie reużywalności to jak zwykle wszystko zależy od projektu i problemu. Ja do tej pory (5 lat hibernate) w większości miałem przypadki, gdzie kwerenda była dynamiczna ponieważ:
- były różne (zmienne) kryteria wyszukiwania
- chciałem chciwie dociągnąć różne zagregowane ancje - w zależności od use case.
Podoba mi się podsumowanie o pochówku - pragmatyczne podejście.
Prześlij komentarz