Saturday, March 19, 2011

Generowanie danych dla widoku

Spotkałem się niedawno z ciekawym rozwiązaniem dotyczącym sposobu generowania danych dla widoku. Warto je opisać.

O co chodzi?
W większości aplikacji musimy dostarczać do widoku różnego dane by je zaprezentować użytkownikowi. Mogą być to dane, na których użytkownik bieżąco pracuje i które widzi na stronie albo dane służące go produkowania raportów, z którymi użytkownik nie wchodzi w interakcję.

Często wystarcza nam przesyłanie do widoku obiektów detached. Przy złożonych modelach domenowych przesyłanie fragmentów modelu domenowego nie jest wygodne. Chociażby z tego względu, że widok często wymaga danych przekrojowych, pochodzących z różnych miejsc modelu. Radzimy sobie wtedy za pomocą obiektów DTO tworzonych na potrzeby konkretnych widoków (Rysunek 1).






Rysunek 1: Generowanie DTO z modelu domenowego

W zależności od skomplikowania obiektów transferowych, generowanie ich można umieścić albo w samych obiektach domenowych albo scentralizować w usłudze Transfer Object Assembler.

Gdy ilość sposobów prezentowania danych dramatycznie wzrasta (raporty, ekrany), okazuje się, że kod związany z obiektami transferowymi zaśmieca projekt i coraz trudniej go utrzymywać.

Pierwsze rozwiązanie
Pierwszy pomysł to przeniesienie odpowiedzialności generowania obiektów DTO na SQL np. z wykorzystaniem biblioteki myBatis. Pamiętajmy, że chodzi o obiekty tylko do odczytu.




Rysunek 2: ORM dla modelu domentowego myBatis dla DTO

Powoduje to dwa problemy. Po pierwsze: znów w miarę rozrastania warstwy widoku a wraz z nią rożnych sposobów (przekrojów) prezentowania danych, zarządzanie zapytaniami SQL nawet umieszczonymi w plikach XML jest uciążliwe. Po drugie pojawia się dualizm: z jednej strony ORM zarządza modelem domenowym, z drugiej wcinamy się gdzieś z boku SQLem, aby pobrać dane dla widoku.

Mapowanie widoków bazodanowych
Rozwiązanie, z którym się spotkałem, polega na zepchnięciu generowania danych przekrojowych bazę danych i reprezentowania ich w postaci widoków bazodanowych. Obiekt DTO mapujemy jako encje na w zdefiniowane w bazie widoki.
W usłudze, dzięki której pobieramy dane z bazy udostępniamy wyłącznie możliwość odczytu.

Ciekawe jakby można nazwać obiekt taką usługę. DAO, Repository? Jakoś kłóci się z tym, co zwyczajowo rozumiemy przez tego typu usługi. Wydaje mi się, że dobrą (na razie ogólną) nazwą będzie PresentationDataService.




Rysunek 3: ORM mapuje widoki bazodanowe

Urzekająca jest symetryczność tego rozwiązania: to co obiektowe jest po stronie obiektów, to co bazodanowe po stronie bazy danych, a ORM wykorzystany jako pomost pomiędzy tymi dwoma światami.

Tymczasowa niespójność danych
W opisanym rozwiązaniu kluczowym aspektem jest wydajność. Po pierwsze: umieszczamy dane dla prezentacji w widoku zmaterializowanym.
Wydajność można dramatycznie poprawić jeśli zgodzimy się na tymczasową niespójność danych. Na przykład: użytkownik dodaje nowego pracownika i otrzymuje komunikat: "Twoje zmiany pojawią się w systemie w ciągu 10 minut.". Okresowe przeliczanie widoku zmaterializowanego jest zdecydowanie wydajniejszym rozwiązaniem niż robienie tego na każde żądanie użytkownika.
Oczywiście jeśli baza danych nie wspiera widoków zmaterializowanych to używamy tabeli tymczasowej i sami klepiemy kod aktualizujący.

Przeszukiwanie dużych ilości danych
Poniższe rozwiązanie dotyczy sytuacji, gdy mamy w bazie kilka milionów np.: klientów oraz złożone kryteria wyszukiwania. Mówiąc o złożonych kryteriach mam na myśli zmienną liczbę parametrów, po których będziemy wyszukiwać (w zależności od potrzeb wynikających z sytuacji biznesowych). Przy stałej liczbie parametrów można stworzyć odpowiednią ilość zapytań QL. Przy zmiennej liczbie parametrów najwygodniej posługiwać się obiektami Cirteria udostępnianymi przez dany ORM. Znów kluczową sprawą jest wydajność przeszukiwania.

Tabela pośrednia z kryteriami wyszukiwania
Pomysł polega na utworzeniu w bazie danych pomocniczej tabeli (widoku zmaterializowanego), która z jednej strony zawiera iloczyn kartezjański wszystkich atrybutów wyszukiwanego obiektu, a z drugiej namiar na odpowiedni wiersz w tabeli związanej z właściwym obiektem, który spełnia kryteria wyszukiwania.





Rysunek 4: Pomocnicza tabela z kryteriami wyszukiwania

Pytanie otwarte: Czy pośrednia tabela kryteriów i odpowiednie jej poindeksowanie rzeczywiście przyśpieszy wyszukiwanie obiektów?