Showing posts with label ORM. Show all posts
Showing posts with label ORM. Show all posts

25 November 2013

Axon framework - behavior driven testing

Rich domain model that emerges from application of DDD techniques has many advantages over anemic model but is hard (or even impossible) to deliver when modeled entities need to be stored in relational database. The reason lays deeply in the mismatch between goals of DDD and ORM models. Without going into details, the main difference is that the goal of ORM models is to enable "easy" persistence/storage and retrieval (including ad-hoc queries) of modeled objects leveraging SQL/RDBMS technology while the goal of DDD models is to decompose complex business domain into manageable pieces taking into account scalability and consistency requirements. Therefore applying both DDD and ORM modeling techniques simultaneously seems to be at least risky endeavor. But with help of Axon framework doing DDD on top of ORM is possible and may lead to better overall design.

In my previous article in this series Axon framework - DDD and EDA meet together (which I recommend to read before proceeding) I have introduced Axon framework and described how it enables application of DDD, CQRS, SAGA design techniques. I have also described key building blocks of DDD (Aggregates and Events) and mentioned event sourcing as "optional building block".
In this article I will show how to upgrade JPA managed entities (shortly persistent entities) that play role of Aggregate Roots (AR) to event sourced Aggregate Roots, so that they can be loaded from an event stream. The goal is not to replace relational database with event store (we are not ready yet to be eventual consistent ;-), but to improve quality of unit tests of ARs (remember, ARs are not simple data container, they contain business logic and thus deserve careful testing). As we will see, having possibility to construct ARs from events will let us write self-documenting unit tests expressed purely in terms of events and commands.
The main problem that needs to be addressed when modeling Aggregate Roots that are both persistent and event sourced entities is lack of consistency boundary concept in ORM model. Persistent entity can hold direct reference to any other persistent entity, while Aggregate Root can't hold direct reference to other Aggregate Root. Let's take a closer look at this problem and try to enumerate possible solutions.

Direct dependencies between Aggregate Roots


We can easily load persistent entity that contains graph of other persistent entities because EntityManager can execute any series of SQL select statements (using joins if possible) while fetching the entity. There is nothing that prevents EntityManager from assembling single graph containing several different persistent entities even if we modeled those entities as different Aggregate Roots. When dealing with event sourced ARs, its not the case. Event sourced repository only knows how to load single AR. Thus, to be able to load AR from event stream we need to ensure that it has no direct dependencies to other ARs. 
Note that it is perfectly valid for AR to keep direct references to owned entities. When loading from event store, AR is responsible for recreating all owned entities.
Of course, by definition AR should not have reference to other AR, but because our ARs are persistent entities direct references in some cases might occur because they are for some reasons convenient or even necessary. Lets think of those reasons.

Entities need to be queried


Sometimes we model dependencies between persistent entities just to be able to perform effective queries using JPQL. The solution in this case is easy: referencing by id.
It turns out we can easily replace object reference to related entity with its identifier (aka primary key) by providing targetEntity parameter in mapping annotation (probably not very commonly used JPA feature):

And that's it. Problem solved.

Entities should not contain duplicated data


There is another reason for corrupting boundaries between ARs when modeling on top of relational database: avoiding data duplication (aka normalization). The normalization leads to complexity that can be easily noticed by looking at any "robust enough" ERD diagram (ex.: online shop data model).
Complexity shows up in the code of business logic. It quickly turns out that logic of any business operation (except some simple CRUD operations) requires access to data from multiply entities.
In example below Payment Plan (AR) (part of operational domain/context) implements register payment operation that performs check if received payment is complete (the received payment amount is equal to amount defined in Payment Plan (AR) (part of planning domain/context)) and thus accesses paymentAmount property of PaymentPlanAR:

We can get rid of direct relationship to payment plan AR by simply duplicating paymentAmount in Payment Plan and Payment Period (we would copy the value from PaymentPlanAR during creation of PaymentPeriodAR). That way we keep both ARs (contexts) separated.
So solution in that case is simple: data duplication.

(There is also a third possibility: model the activity as business process (SAGA) that can interact with many ARs, but we will not consider this option here).

OK, but lets assume we can't do it (duplicate data). The reason we can't could be that we want payment amount to be editable and we don't want to care about synchronization of changes between payment plan and payment period, assuming such synchronization would be necessary...

Shortly, lets assume we need to keep direct references between some ARs...

Now, we still want to be able to construct ARs from events for testing purposes.
Fortunately, Axon framework allows registering AggregateFactory that can assist AR construction and initialization from event stream.

Aggregate Factory


Lets introduce Subscription Plan (AR) that holds reference to Subscription Pool (AR):

When loading the subscription plan from jpa repository (production environment), the pool will be automatically initialized by underlying JPA provider. When loading from event sourced repository (test environment) Axon framework will check if AggregateFactory is registered for SupscriptionPlanAR type and call AggregateFactory#doCreateAggregate method providing aggregate identifier and first event from the event stream to create empty AR instance (AggregateFactory does not fill business properties of AR, they will be initialized when applying events from event stream).

Thus, we need to implement SubscriptionPlanFactory capable of instantiating SubscriptionPlanAR with SubscriptionPoolAR injected:

Logic inside SubscriptionPlanFactory#instantiate is also used by command handler when processing SubscriptionPlanCreateCommand so good idea is to promote SubscriptionPlanFactory to standard factory for SubscriptionPlanAR that is not only able to instantiate empty aggregate root but also create new valid aggregate root as dictated by business requirements. To do so we will add public create method to the factory:


Please note that event sourced ARs can't rely on auto-generated identifiers (identifiers generated after AR is saved to database), that's why our factory generates identifier before creating AR (previously it was responsibility of command handler).

Please also note that main construction logic (application and handling of SubscriptionPlanCreatedEvent) is still placed inside AR itself, but now it is contained in create method instead of constructor:

Worth noting is separation between event publishing (create method) and event application (handle method). Previously (without support for event sourcing) both operations could be implemented in one method (or constructor) (see: Account.java), now they need to be split so that loading aggregate root from event stream is possible.

Now, implementation of command handler is straightforward:

Test fixture


We are now ready to configure Axon's given-when-then test fixture that can be used for testing different types of Aggregate Roots:


Concrete implementation of test class will need to define aggregate type, aggregate factory (Axon provides GenericAggregateFactory that can be used if no custom initialization of AR is required) and command handler. Both aggregate factory and command handler are then passed to specialized registration methods of Axon's FixtureConfiguration. At the end, command handler is passed reference to repository that was created by GivenWhenThenFixture (in production scenario command handler works with JPA-based implementation of Repository interface, in test scenario the repository (of class EventSourcingRepository) and event store are constructed by Axon's test framework).

Finally we can implement unit tests by simply declaring commands and events using given-when-then style as following:

 - Given: a set of historic events
 - When: I send a command
 - Then: expect certain events

Sometimes it is shorter to declare commands instead of events in given section as one command can result in multiply events. Axon supports that too. It is also possible to assert return value or exception returned/thrown by command handler. It should be noted that all events or commands should refer only to single AR. If you want to test Saga classes (interactions between different ARs) you need to use AnnotatedSagaTestFixture.

Let's see couple of example tests:



What happens in the background when test is executed can be described in a few steps:
 - given section
    - given events (or events published as the result of given commands execution) are saved to in-memory event store
 - when section
    - command handler is invoked
    - command handler asks repository to load aggregate root by id
    - repository loads AR from event store (aggregate factory creates empty AR and then events are applied to that AR)
    - command handler invokes appropriate business method on AR

Looking at test class you can see that there is no code related to infrastructure (no dependency to jpa, sql import statements, etc.), we don't care about persistence layer at all. We test external interface represented by command (input) and events (output) decoupling tests from actual AR being tested. Tests are self-explanatory out of the box. Creating a test in this way could be easily supported by some gui tool that would allow building the test by dragging widgets representing events, commands, entities and exceptions into a form (containing given, when, then section) and filling their properties :)

22 February 2011

JPA - Stronicowanie wyników kwerendy

Interfejs kwerendy zdefiniowany w JPA (javax.persistence.Query) umożliwia stronicowanie listy wyników (paging). Służą do tego metody:


Wynikiem kwerendy ze stronicowaniem jest podzbiór obiektów czyli strona, której numer i rozmiar określają odpowiednio parametry startPosition i maxResult.

Stronicowanie przyspiesza działanie aplikacji (mniejsza ilość danych do przetwarzania) oraz ułatwia użytkownikowi nawigację i wyszukiwanie określonych rekordów.

Jednak jak to zwykle w świecie ORM bywa, każde rozwiązanie ma swoje "problemy", które w przypadku stronicowania objawiają się wraz z użyciem mechanizmu ładowania wyprzedzającego elementów kolekcji.

Ładowanie wyprzedzające ze stronicowaniem


Ładowanie wyprzedzające generalnie polega na wykonaniu kwerendy w taki sposób, aby razem z głównymi obiektami pobrane zostały obiekty powiązane (pojedyncze obiekty bądź kolekcje).

Najbardziej popularnym sposobem ładowania wyprzedzającego jest złączenie tabel w kwerendzie (join fetching). Niestety, metoda ta nie nadaje się do zapytań ze stronicowaniem. Zobaczmy dlaczego.

Przykład


Mamy obiekt zamówienia (Order) zawierający pozycje (LineItem):


Załóżmy, że chcemy wyświetlić użytkownikowi stronę z listą zamówień. Jednocześnie dla każdego zamówienia chcemy załadować jego pozycje. W tym celu tworzymy kwerendę na obiekcie Order, ze stronicowaniem oraz ze złączeniem kolekcji lineItems:


Złączenie definiujemy w JPQL za pomocą klauzuli JOIN FETCH.

Oczekiwania wobec dostawcy JPA

Załóżmy, że w bazie mamy 3 zamówienia z różną ilością pozycji.
Oczekujemy, że zarządca utrwalania (EntityManager) wykona pojedyncze zapytanie sql (z klauzulą left outer join) i zwróci listę zawierającą 3 obiekty Order. Dodatkowo oczekujemy, że w każdym obiekcie Order, kolekcja lineItems będzie załadowana.

Realizacja

- Hibernate

Hibernate co prawda zwraca oczekiwany wynik, ale generuje podejrzanie brzmiący komunikat:
firstResult/maxResults specified with collection fetch; applying in memory!

- EclipseLink

EclipseLink zwraca 2 obiekty Order.

Wyjaśnienie

Aby zrozumieć co się stało, zobaczmy jak dokładnie wygląda wynik naszej kwerendy na poziomie rekordów bazy danych bez uwzględnienia stronicowania:

| order_id | cust_id | cust_name | line_id | product_sku |
----------------------------------------------------------------
| 1 | 1 | Jan Kowalski | 1 | 232342342 |
| 1 | 1 | Jan Kowalski | 2 | 345345443 |
| 2 | 2 | Paweł Kaczor | 3 | 655624323 |
| 3 | 3 | Jerzy Dudek | 4 | 673454345 |
| 3 | 3 | Jerzy Dudek | 5 | 563425676 |
| 3 | 3 | Jerzy Dudek | 6 | 234576854 |

Widzimy, że w wyniku złączenia, otrzymujemy zbiór rekordów liczebnością przekraczający ilość zamówień. Jeśli z takiego zbioru będziemy chcieli wyciągnąć stronę o rozmiarze 3, otrzymamy tylko pierwsze trzy rekordy zawierające zamówienia z id 1 i 2. Zamówienie z id 3 zostanie wykluczone. Otrzymamy zatem dwa zamówienia zamiast trzech! Wniosek: stronicowanie na poziomie rekordów w kwerendzie ze złączeniem outer join jest niedokładne.

Teraz już wiemy dlaczego EclipseLink zwrócił dwa zamówienia. Ale jakim sposobem Hibernate zwrócił trzy zamówienia? Odpowiedź jest prosta (ale bolesna). Hibernate omija problem poprzez załadowanie wszystkich rekordów z tabeli i wyselekcjonowanie strony w pamięci (stąd magiczne: "applying in memory"!). Rozwiązanie to zwiększa zużycie zasobów (procesora i pamięci), co przeczy głównemu celowi (zwiększeniu wydajności), dla którego stosujemy stronicowanie. Należy poszukać lepszego rozwiązania.

Ładowanie wsadowe ze stronicowaniem


Ładowanie wsadowe (batch fetching) to bardziej zaawansowany sposób pobierania wyprzedzającego. Obiekty powiązane nie są ładowane w kwerendzie głównej, ale w dodatkowej kwerendzie, której ostateczna postać zależy od wybranego (o ile dostawca na to pozwala) typu ładowania wsadowego.

Stronicowanie wykonywane jest bezproblemowo w kwerendzie głównej, która nie zawiera klauzuli outer join.

Konfiguracja - Hibernate

Dla kolekcji lineItems specyfikujemy ładowanie wsadowe używając adnotacji @org.hibernate.annotations.BatchSize:


Parametr size w adnotacji @BatchSize oznacza ilość elementów kolekcji, jaka zostanie załadowana w pojedynczej kwerendzie sql.

Konfiguracja - EclipseLink

Ten sam sposób ładowania wsadowego konfigurujemy w EclipseLink następująco:


Realizacja

Tworzymy kwerendę JPA tym razem bez klauzuli JOIN FETCH. W celu lepszego zobrazowania działania pobierania wsadowego, dodajemy warunek na pole orderDate.


Wygenerowane zostają dwa zapytania sql:

- kwerenda główna zamówień (ze stronicowaniem)

- kwerenda dodatkowa - załadowanie wsadowe pozycji zamówień

Jak widzimy, w celu załadowania pozycji tylko dla zamówień pobranych w kwerendzie głównej, w kwerendzie dodatkowej została użyta klauzula IN.

Optymalizacja - EclipseLink

EclipseLink pozwala skonfigurować trzy typy pobierania wsadowego: (IN, JOIN, EXISTS)
Typ IN już znamy. Mankamentem jest tutaj ograniczona ilość elementów kolekcji, które mogą być załadowane w jednej kwerendzie sql. Efektywniejszym rozwiązaniem jest użycie kryteriów selekcji z kwerendy głównej w kwerendzie dodatkowej (typ JOIN - "The original query's selection criteria is joined with the batch query").

Konfiguracja:


Wygenerowana kwerenda dodatkowa wygląda następująco:


Widzimy, że problematyczna klauzula IN zastąpiona została kryterium wyboru identycznym jak w kwerendzie głównej.

Podsumowanie


W powyższym artykule przedstawiłem w jaki sposób stosować stronicowanie (paging) w kwerendach JPA. Szczegółowo omówiłem problem stronicowania, kiedy w kwerendzie używane jest ładowanie wyprzedzające elementów kolekcji.

4 October 2010

Dostrajanie warstwy ORM w projekcie wielomodułowym

Częstym jak sądzę przypadkiem w średnich i większych projektach informatycznych jest współdzielenie modelu domeny przez kilka niezależnych aplikacji.
Takimi aplikacjami mogą być np.: web portal dla klientów, wewnętrzna aplikacja administracyjna, moduł raportujący.
Wspólne dane, z których korzystają aplikacje, nie są wcale powodem do tworzenia wspólnego modelu domeny. Polecam na ten temat prezentację DDD - putting model to work), której którkie podsumowanie można znaleźć tutaj: IT-Researches Blog.
Zakładając jednak, że mamy jeden model (co jest częstą praktyką) pojawia się kwestia współdzielenia modelu ORM zdefiniowanego
jako mapowania obiektów do tabel w bazie relacyjnej.
Jak się bowiem często okazuje wymagania poszczególnych aplikacji w tym zakresie są różne.
Dotyczyć to może takich kwestii jak sposób inicjalizacji pól encji (lazy vs egear fetching).
Zagadnienie, jakie dokładnie ustawienia ORM warto dostrajać i kiedy, odłożę na później.
W tym wpisie chciałbym przedstawić w jaki sposób skonfigurować projekt aby umożliwić poszczególnym aplikacjom dostosowanie warstwy ORM do ich potrzeb oraz jakie problemy
przy tworzeniu takiej konfiguracji napotkałem.

Konfiguracja projektu


Wykorzystywane technologie:
- Maven
- Spring
- Hibernate

Mamy zatem projekt wielomodułowy, w skład którego wchodzą poszczególne aplikacje oraz następujące moduły współdzielone:

- model domeny - (encje/domain objects)
- dao - konfiguracja dostępu do bazy danych, klasy dao

Moduł - model domeny

Model domeny stanowią encje (obiekty POJO) opisane adnotacjami Hiberanate Annotations.
Adnotacje są dobrym sposobem na zdefiniowanie domyślnych mapowań ORM. Poszczególne aplikacje mają bowiem możliwość nadpisania domyślnych mapowań przy użyciu plików konfiguracyjnych xml (hbm.xml). Zwracam uwagę na to, że Hibernate Annotations bazują na specyfikacji JPA jednak nie wymagają użycia modułu JPA (dostarczającego interfejs javax.persistence.EntityManager).

Moduł - dao

Konfigurację SessionFactory tworzymy wykorzystując Spring-ową fabrykę wspierającą Hibernate Annotations.



W parametrze annotatedClasses podajemy listę naszych encji. Co warte uwagi Spring umożliwia wskazanie pakietu który będzie automatycznie skanowany w poszukiwaniu encji (parametr packagesToScan).

Nas jednak bardziej interesuje parametr mappingDirectoryLocations. Wskażemy w nim katalog, z którego załadowane zostaną pliki hbm.xml.
W ten sposób umożliwiamy aplikacjom dostarczenie własnych mapowań ORM.

Przykład


Uporawszy się z konfiguracją, przetestujmy jak działa nadpisywanie mapowań na konkretnym przykładzie.

Mamy zatem klasę FeedCategory, która dziedziczy po BaseEntity i zawiera listę podkategorii (pole subCategories).



Jak widzimy, domyślnie Hibernate załaduje listę podkategorii leniwie (w momencie użycia) co zostało zdefiniowane ustawieniem fetch = FetchType.LAZY.
Załóżmy jednak, że chcemy aby w naszej aplikacji podkategorie były ładowane "chciwie" (ang. eagerly) a więc zaraz po załadowaniu obiektu głównego.

W tym celu tworzymy w module konkretnej aplikacji katalog orm/custom-mappings, który wskazaliśmy w konfiguracji SessionFactory (w projekcie maven-owym umieszczamy ten katalog w gałęzi src/main/resources) i umieszczamy w nim plik feedCategory.hbm.xml:



Tym razem ustawienie sposobu pobierania listy kategorii definiujemy atrybutem lazy="false" (czyli chciwie).

Problem


Napotykamy problem, który wydawało się nie powinien zaistnieć. Mianowicie adnotacja @MappedSuperclass nie ma odpowiednika w konfiguracji mapowań Hibernate.

Obejściem tego problemu jest zdefiniowanie pól z klasy BaseEntity w pliku mapowań klasy FeedCategory. Jednak jest to niewygodne. Wyobraźmy sobie bowiem, że nadpisujemy 10 klas po czym dokonujemy zmiany w domyślnej konfiguracji BaseEntity... Będziemy musieli tę zmianę wprowadzić również w 10 plikach hbm.xml..
Drugim problemem (który być może wynika z pierwszego - temat nie do końca sprawdzony) jest konieczność zdefiniowania wszystkich pól klasy FeedCategory.
Nie można zatem nadpisać tylko zmienionego elementu konfiguracji, trzeba zdefiniować całe mapowanie na nowo.

Rozwiązanie


Rozwiązaniem powyższych niedogodności jest skonfigurowanie Hibernate jako dostawcy JPA i zastąpienie mapowań w formacie hbm.xml mapowaniami xml w standarcie JPA.

W tym celu konfigurację SessionFactory zastępujemy konfiguracją EntityManagerFactory ponownie korzystając z udogodnień jakie oferuje Spring, tym razem dla JPA:



Szczegółowe ustawienia dostarczamy w pliku persistence.xml, w którym również specyfikujemy listę naszych encji
(ustawiając parametr hibernate.archive.autodetection nakazujemy Hibernate Entity Manager aby wyszukał encje w określonych lokalizacjach,
więcej informacji na ten temat tutaj: Do I need class elements in persistence.xml):


Pozostaje skonfigurować wykrywanie mapowań xml dostarczonych przez poszczególne aplikacje.
Niestety w przypadku JPA nie mamy analogicznego do mappingDirectoryLocations parametru zarówno na poziomie konfiguracji w pliku persistence.xml jak i udogodnień Spring-a.
Rozwiązaniem jest przekazanie do LocalContainerEntityManagerFactoryBean klasy implementującej interfejs
PersistenceUnitPostProcessor. Postprocesor ma możliwość modyfikowanie opcji konfiguracyjnych, w tym dodanie mapowań xml.



Możemy zatem w aplikacji nadpisać mapowania domyślne tworząc plik orm.xml (jest to standardowa nazwa pliku określona w specyfikacji JPA, aczkolwiek plików z mapowaniami może być wiele).
W naszym przykładzie plik orm.xml wygląda następująco:



Jak widać, ostatecznie udało się osiągnąć cel czyli nadpisać tylko to co wymagało dostosowania.
Niestety wymagało to zmiany konfiguracji projektu w celu integracji standardu JPA.