Temat: Repozytorium obiektów (encji)

Ogólnie i niezbyt precyzyjnie - wzorzec zapewnia dostęp do puli obiektów biznesowych (encji) przy ukryciu warstwy (dostępu do danych), która te obiekty tworzy i zwraca.

Poniżej wypunktowałem garść informacji związanych ze wzorcem repozytorium.

1. W warstwie modelu mamy jedynie obiekty reprezentujące nasze dane - zero logiki je tworzącej, zero operacji bazodanowych. Mogą być one np. w standardzie POJO/POCO, co bardzo ułatwia przesyłanie ich między warstwami (DTO). Czyste pole, właściwości i kolekcje (puryści unikają nawet pól wyliczanych i jakichkolwiek metod) i obowiązkowo żadnych odniesień do jakiegokolwiek providera danych, np. atrybutów (adnotacji) mapujących, odniesień do sesji, połączeń, etc. Pure data.

2. Nie interesuje nas, czy obiekty te zostaną utworzone przez jakiś ORM (np. NHibernate, Entity Framework, Sooda czy inne), czy też będą tworzone "na piechotę" z "selektów". I tym bardziej nie interesuje nas, jaka to będzie baza danych (bo to dba akurat warstwa ORMa) - od XML przez SQLite po Oracle. Już sam ORM (lub inny provider) separuje nas od dostawcy bazy danych, ale my potrzebujemy jeszcze jedną warstwę - odseparowania od ORMa. Bo jednak może się zdarzyć, że konkretny ORM "dogaduje się" tylko z kilkoma bazami danych, z których akurat żadna nam, z różnych względów, nie pasuje. Albo, z różnych względów, ORMa zastosować nie chcemy.

3. Z p.2 wynika, że jeśli w projekcie zajdzie potrzeba wymiany bazy danych, bądź ORMa na inny, nie trzeba będzie ruszać naszego modelu - i niczego "wzwyż" (kontrolerów/prezenterów, widoków). Mówiąc obrazowo, "goście od "domain model" mogą iść w tym czasie na urlop". Warto to podkreślić, bo to jest główna zaleta repozytorium.

PS: w tym momencie ktoś może zwrócić uwagę, że ORM stanowi przecież repozytorium obiektów. Owszem, stanowi, ale jest zbyt silnie powiązany z konkretnym silnikiem bazy danych. Poza tym, jak już wspomniałem, nie zawsze chcemy z niego korzystać, np. ze względów licencyjnych, bezpieczeństwa, wydajności, umiejętności zespołu, wymagań klienta potrzebna będzie jego wymiana. Trudno teraz rzeźbić po klasach modelu, przerabiając środowisko po Hibernate na środowisko np. Entity Framework, albo na "czyste selekty" i DataAdaptery czy nawet DataReadery (.NET).

4. Każde repozytorium, związane z daną encją, będzie posiadać standardowy interfejs CRUD:
* Create (zapisz dane do bazy),
* Read (odczytaj dane z bazy),
* Update (uaktualnij dane w bazie),
* Delete (usuń dane z bazy).

Wszystko powyżej repozytorium (logika biznesowa, widoki) widzieć będą jedynie te operacje.

----------
I tutaj kończą się w zasadzie informacje o repozytorium. Diagramy UML i przykładowe implementacje są dostępne w sieci. Natomiast z repozytorium wiąże się nierozerwalnie kilka zasadniczych kwestii i dodatkowe wzorce, o których też należy wspomnieć.
----------

5. Problem pojawia się w momencie pytania o implementację operacji "Read". Przyjrzyjmy się mu bliżej. W przypadku usunięcia, update'u czy utworzenia danych wystarczy w argumencie odpowiedniej metody (C/U/D) przekazać po prostu żądany obiekt (a po "drugiej stronie" przykładowo Hibernate już będzie wiedział, jak się nim zająć).

Ale jak zadać zapytanie w sposób inny, niż "query by example" (dla którego przekazanie obiektu-przykładu by wystarczyło; jednak samo QeE to nieco biednie...), nie wiedząc, "kto stoi po stronie dostawcy danych"? Nie możemy użyć HQL, czy Criteria (bo wiążemy się z Hibernate), nie możemy użyć SQL, no bo którego? Owszem, można przyjąć jakiś standard, np. dawny 92, nowszy 2003, bez ŻADNYCH rozszerzeń. Jednak nawet to może być zbyt wiele, bo przecież te standardy są bardzo rozbudowane, a co, jeśli providerem jest np. ODBC Excela? Nie wiemy, "kto odbierze" nasze zapytanie i jak je zrozumie (jak szeroki zakres zapytań rozumie np. "czytnik XMLa").

W ramach implementacji funkcjonalności Read stosowane są różne podejścia, które starają się wyeliminować z warstwy logiki biznesowej takie "engine dedicated queries". Np. klasa repozytorium dziedziczy po interfejsie CRUD zawierającym listę kilku(dziesiesięciu) metod odpytujących o konkretne rzeczy, np. "średnia zarobków pracowników pomiędzy dwiema datami". Interfejs ten jest implementowany przez providera danych, który generuje już konkretne zapytania. Zauważmy, że w tej sytuacji nie ma możliwości zbytniego wpływu na zapytanie. Można jedynie do pewnego stopnia parametryzować taką metodę odpytującą. Często jednak nie obejdzie się bez rekompilacji kodu przy zmianie zapytania po stronie modelu (np. nie średnia ogólna, ale w grupach stażu pracy).

6. Można sobie poradzić z tym problemem wykorzystując np. technologię LINQ - język zapytań niezależny od sterownika bazy danych i oferujący tylko najprostsze konstrukcje. Sterowników LINQ dla różnych ORMów (a nawet dla XMLa) jest już trochę (ostatnio nawet dla NHibernate, gdzie mamy mapowanie LINQ => Criteria), a dodatkowo można sobie samemu napisać własny translator wyrażeń LINQ na konkretny język zapytań. Nie dość, że uzyskujemy kontrolę typów na etapie pisania kodu, to jeszcze jesteśmy niezależni w dużej mierze od tego, co będzie źródłem danych (LINQ dla NH można zastąpić LINQ dla SQL, Entity Framework, XML, czy własnych struktur). Jeśli na danej platformie nie jest dostępna technologia LINQ (chyba na razie tylko w .NET), pozostaje pisanie sparametryzowanych klas zapytań, albo napisanie czegoś na kształt LINQ. Na jednych platformach, zwłaszcza wyposażonych w mechanizm refleksji (Java, .NET) pójdzie to prosto i uzyskamy gratis kontrolę typów na etapie pisania kodu, na innych - może być ciężej i bez kontroli typów.

Pojawia się zatem bardzo typowy konflikt interesów: odseparowanie od providera danych za cenę uogólnienia języka zapytań i rezygnacji ze specyficznych konstrukcji.

Nasz uniwersalny język zapytań nie będzie oferował bardziej egzotycznych konstrukcji, niż proste odpytywanie z filtrowaniem, być może z zapytaniami zagnieżdżonymi (ale pewnie już tylko po stronie FROM, bo niekoniecznie WHERE (czyli np. EXISTS, IN) czy SELECT ( select (select nazwa from t as t1 where t.id=t1.id) as kolumna from t), być może bez możliwości sortowania danych po agregacji (np. LINQ to NHibernate nie wspiera na razie select/group by/order).

7. Czy to wydajne rozwiązanie? To oczywiście zależy od tego, co robimy. Jeśli projekt jest pisany dla wymagającego klienta (w sensie: duża liczba transakcji, złożone transakcje, bezpieczeństwo dostępu do danych wykluczające jakiekolwiek obce, a już nie daj Boże opensource'owe frameworki, typu Hibernate), typu banki, wojsko, systemy BI, to zapewne pominiemy nawet ORMa i będziemy wprost rzeźbić SQLe, wykorzystując jak najwięcej dobrodziejstw konkretnego silnika bazy danych i optymalizując kod ściśle pod niego. Oczywiście i tutaj będzie potrzebne napisanie swojego DAL (wzorzec Data Access Layer), który nieco odseparuje bazę od modelu.

Ale jeśli piszemy mniej złożone oprogramowanie, np. aplikacja desktopowa, mniej złożony system ERP (uwaga na raportowanie, wymagające nieraz wyrafinowanych sztuczek), serwis internetowy, na który liczba wejść nie będzie porównywalna z portalami informacyjnymi i nie stosujemy bardzo złożonych zapytań, dynamicznego SQLa i innych cudów, albo możemy je przerzucić do procedur wbudowanych (tu kolejna dyskusja za i przeciw), to możemy wprowadzać kolejne warstwy, które - chociaż początkowo będą zniechęcać dodatkowym, sporym narzutem technologicznym, to potem szybko się zwrócą przy zmianie technologii, naprawach i rozszerzaniu systemu, a na pewno w fazie produkcji.

8. Kolejne zagadnienie, to kwestia posiadania dużej liczby repozytoriów (przyjmuje się często zasadę - jedno repozytorium na encję). Do ich "ogarnięcia" i automatycznego zarządzania nimi wykorzystuje się np. wzorzec Service Locator, który automatycznie wyszukuje wszystkie repozytoria, tworzy ich instancje, dodaje do wewnętrznej kolekcji i udostępnia je na żądanie. Świetnie sprawdza się tutaj wzorzec Fabryka (repozytoriów), oparta o klasę szablonową.

Przykład rozwiązania (działającego, co nie znaczy optymalnego :) ): http://www.goldenline.pl/forum/1820750/repozytorium-en...Adrian Olszewski edytował(a) ten post dnia 01.08.10 o godzinie 18:21
Grzegorz L.

Grzegorz L. Software Developer

Temat: Repozytorium obiektów (encji)

A można prosić o nazwę grupy, na której forum jest ten post z przykładem?
Bo jeśli ktoś w grupie nie jest, to nawet nie wie która to, a dostęp ma zablokowany :)

Temat: Repozytorium obiektów (encji)

Programiści .NET. Miałem przekleić cały długi post, ale wtedy "zerwie się referencja" na tamtejszą dyskusję :) Ponieważ jednak tam członków zatwierdza moderator (z uwagi na wyjątkowe natężenie złośliwych spamerów w ostatnim czasie), ktoś może nie chcieć czekać, albo w ogóle dopisywać się do grupy, to wtedy na życzenie przekleję posta i odpowiedzi.

A przy okazji:
*Co to jest LINQ

*SBQL4J - Javove podejście do LINQ (Zajawka na GL)
An extension to Java language with an engine based on Stack-Based Architecture (SBA)

It provides capabilities similar to Microsoft LINQ for Java language. Allows to process Java objects with queries.

SBQL4J follow software engineering principles such as orthogonality, modularity, minimality, universality, typing safety, and clean, precise semantics.

Code with queries are parsed by preprocessor integrated with OpenJDK Compiler. SBQL4J builds AST query trees which are then analyzed and as output Java code is produced.


It's 100% compatible with current Java Virtual Machines, and can be safely used in any Java project (compatible with Java 6).

* Dyskusja na temat rozwiązań a'la LINQ dla Javy i jeszcze tu.

PS: przykładowe, proiściutkie wykorzystanie w kodzie:
List<MedicalUnit> units = ( from unit in Utils.InterTaskDTO.Instance.RepoLocator.GetRepository<MedicalUnit>().GetByLINQ()
where unit.Name.Contains(name)
&& unit.PhoneNumbers.Any(num => num.Number.Contains(phone))
&& unit.Address.Street.StartsWith(street)
&& unit.Address.Voivodeship.StartsWith(voivodeship)
&& unit.Address.City.StartsWith(city)
&& unit.Address.PostalCode.StartsWith(postalCode)
select unit).ToList<MedicalUnit>();
Adrian Olszewski edytował(a) ten post dnia 01.08.10 o godzinie 22:35

Następna dyskusja:

Abstract Object Factory (Fa...




Wyślij zaproszenie do