Posty otagowaneORM

Fluent Nhibernate Rocks!!!

fluent-nhibernate-logo

W ramach wolnych chwil w drodze do pracy staram się zapoznać z nowymi frameworkami. Jedną z tych nowinek jest open sourcowy projekt Fluent NHibernate. Jak sama nazwa wskazuje jest to projekt powiązany z bardzo znanym mapperem obiektowo – relacyjnym NHibernate. Hasło fluent z kolei tyczy się ostatnimi czasy bardzo popularnego wzorca Fluent Interface. Wzorzec ten oparty jest przede wszystkim na łańcuchowym wywoływaniu metod API, poprzez które możliwe jest stworzenie składni języka zwanego powszechnie DSL (Domain Specific Language). Język DSL pozwala na pisanie bardziej „wyrazistego” kodu źródłowego programu, koncentrującego się na konkretnej dziedzinie, przez co osoba znająca daną domenę jest w stanie bardzo szybko pojąc jaka logika się kryje za tym kodem.

W takim razie warto sobie zadać na wstępie pytanie jakiej dziedziny dotyczy Fluent NHibernate? Wydaje mi się, że dla tych osób, które miały do czynienia z mapperem NHibernate pytanie to nie jest trudne dlatego, że zapewne nieraz musiały pokonywać problemy wywołane poprzez błędnie zdefiniowane metadane mapowanie obiektów domenowych. W NHibernacie metadane domyślnie definiowane są w postaci XML’a, jedyną alternatywą do tej pory było stosowanie atrybutów, którymi należy dekorować klasy domenowe.

Jedno i drugie rozwiązanie ma swoje wady i zalety. Zaletą XML’a w porównaniu do atrybutów jest przede wszystkim izolacja modelu domenowego od wszelkiego rodzaju kodu infrastrukturalnego, którym są właśnie metadane mapowania. Zaś atrybuty eliminują ciężkie do definiowania i modyfikacji pliki XML, które są piętą achillesową nie tylko NHibernate’a. Oba rozwiązania mają również pewne wspólne wady. Jedną z najpoważniejszych moim zdaniem, jest zbyt duży nakład pracy dla osób początkujących w celu zdefiniowania podstawowego mapowania. Wiąże się to przede wszystkim z dużą liczbą prób i błędów w trakcie doboru odpowiednich opcji mapowania. Oczywiście z czasem, gdy już poznamy, które opcje idą ze sobą w parze okazuje się, że ich duża dostępna liczba jest wielką zaletą NHibernate’a. Nie oznacza to jednak, że nie popełnia się czasami trywialnych błędów w definicji XML’a lub atrybutach.

Kolejnym ważnym problemem jest brak lub ograniczona możliwości refaktoryzacji modelu domenowego bez wymuszania zmian w metadanych mapowania. W przypadku użycia plików XML, każda zmiana nazwy klasy lub jednej z jej właściwości, wymusza również modyfikację zawartości pliku, co łatwo prowadzi do błędów pozwalających „położyć” całą aplikację. Ten problem jak i możliwość prostego automatycznego testowania mapowania, był jednym z podstawowych powodów wskrzeszenia projektu Fluent NHibernate. Co najlepsze, z czasem okazało się, że można usprawnić jeszcze wiele innych funkcjonalności.
Rozpoczynając moją przygodę z tym projektem znałem już pewne jego możliwości dlatego, że dużo informacji pojawiło się w międzyczasie na blogach, które czytam na co dzień. Mimo to zostałem bardzo pozytywnie zaskoczony funkcjonalnością pozwalającą na wyeliminowanie pliku XML konfigurującego mapper. Obecnie w celu stworzenia obiektu SessionFactory komunikującego się z bazą danych MS SQL 2005 wystarczy napisać następujący kod.

var factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(s=>s.FromConnectionStringWithKey(“Default”)))
.BuildSessionFactory();

Najpiękniejsze w tym rozwiązaniu jest to, że connection string potrzebny do połączenia się z bazą danych jest pobierany z sekcji connectionStrings pliku konfiguracyjnego aplikacji. Taka prosta rzecz a jak cieszy :) , a administratora jeszcze bardziej ucieszy dlatego, że nie będzie musiał przedzierać się przez Überconifgi w celu wprowadzenia prostych zmian.

Domyślnie w ramach Fluent NHibernate zostały zdefiniowane klasy reprezentujące konfigurację większości obsługiwanych przez NHibernate baz danych. Odziwo nie można znaleźć na dzień dzisiejszy klasy odpowiedzialnej za konfigurację Oracla. To jednak nie jest żaden problem dlatego, że napisanie tego typu klasy wymaga od nas jedynie dziedziczenia po klasie PersistenceConfiguration i ustawienia odpowiedniego drivera oraz innych koniecznych wartości ,takich jak choćby dialect. Poza tym należy zwrócić uwagę na to, iż projekt jest cały czas w trakcie developmentu i zapewne dojdzie do niego jeszcze wiele dodatkowych funkcjonalności.
Przejdźmy teraz do największej perełki, czyli do definiowania metadanych mapowania. Dla tych osób, które nie miały do czynienia z definiowaniem mapowania przy użyciu plików XML chętnie przedstawię jak to wygląda.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:NHibernate-mapping-2.2" default-lazy="true" assembly="Examples.FirstProject" namespace=" Examples.FirstProject.Entities">
  <class name="PaymentMethod" table="PaymentMethod" xmlns="urn:NHibernate-mapping-2.2">
    <id name="Id" column="Id" type="Int32">
      <generator class="hilo">
        <param name="max_lo">100</param>
      </generator>
    </id>
    <property name="IsActive" type="Boolean"></property>
    <many-to-one name="Owner" column="OwnerId" />
    <joined-subclass name="Examples.FirstProject.Entities.DirectDebit, Examples.FirstProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <key column="DirectDebitId" />
      <property name="MandateCopyIsAvailable" type="Boolean"></property>
      <property name="HolderName" type="String"></property>
    </joined-subclass>
    <joined-subclass name="Examples.FirstProject.Entities.CreditCard, Examples.FirstProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <key column="CreditCardId" />
      <property name="CreditCardNumber" type="String"></property>
      <property name="Expires" type="DateTime"></property>
      <property name="HolderName" type="String"></property>
    </joined-subclass>
  </class>
</hibernate-mapping>

Okey, wiem, że wyskoczyłem z grubej rury, ale takie jest już życie programisty – przykłady są zawsze proste, a codzienne zmagania nie. Dlatego też przedstawiłem przykładowe mapowanie modelu podobnego do tego, z którym mam do czynienia w pracy. W celu wyjaśnienia, wyżej przedstawione metadane dotyczą dwóch klas abstrachujących formy płatności (CreditCard i DirectDebit) dziedziczących po klasie bazowej (PaymentMethod), która z kolei ma referencję typu wiele do jednego do klasy Customer. Jako, że bazy relacyjne nie obsługują dziedziczenia, zmuszeni jesteśmy do skorzystania z dostępnych strategii obsługi dziedziczenia w NHibernate. W powyższym przypadku użyta została strategia mapowania każdej klasy w hierarchi do osobnej tabeli zwana class table inheritance.
domain-maping
Plik XML nie należy znów do największych, ale warto zobaczyć jak łatwo można zredukować ilość kodu stosując Fluent NHibernate.

public class PaymentMethodMap : ClassMap
{
public PaymentMethodMap()
{
Id(pm => pm.Id).GeneratedBy.HiLo(“100″);
Map(pm => pm.IsActive);
References(pm => pm.Owner).TheColumnNameIs(“OwnerId”);
JoinedSubClass(“CreditCardId”, cp =>
{
cp.Map(creditCard => creditCard.HolderName);
cp.Map(creditCard => creditCard.Expires);
cp.Map(creditCard => creditCard.CreditCardNumber);
});
JoinedSubClass(“DirectDebitId”, cp =>
{
cp.Map(directDebit => directDebit.ReferenceNumber);
cp.Map(directDebit => directDebit.HolderName);
cp.Map(directDebit => directDebit.MandateCopyIsAvailable);
});

}
}

Definiowanie metadanych mapowania przy wykorzystaniu Fluent NHibernate odbywa się poprzez tworzenie klasy dziedziczącej po klasie szablonowej ClassMap z parametrem klasy, którą mamy zamiar zmapować. Dzięki temu zyskujemy wsparcie intelisensu w trakcie mapowania poszczególnych właściwości mapowanej klasy oraz weryfikację poprawności kodu w trakcie kompilacji. Najczęściej używaną metodą udostępnioną przez ClassMap jest metoda Map, która pozwala na mapowanie właśćwiości klasy do kolumny w tabeli. Ta, jak i pozostała większość metod, wykorzystuje Expression Trees, w celu pozyskiwania nazw mapowanych klas i ich właściwości, dlatego też przedstawiony powyżej kod jest głownie opart o lambda expressions. Ze względu na to, że poszcególne użyte metody opisane są szczegółowo w dokumentacji projektu, nie ma sensu bym się na nich koncentrował.

Jedyne co jeszcze pozostaje do zrobienia, by skorzystać z wyżej przedstawionego mapowania, to zmodyfikowanie kodu tworzącego SessionFactory.

var factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(s=>s.FromConnectionStringWithKey(“Default”))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf())
.BuildSessionFactory();

Metoda Mappings pozwala na zdefioniowanie typu metadanych mappingu, którego chcemy użyć. Ze względy na to, że w przykładzie została użyta forma mapowania oparta o kod C#, wybrano opcję FluentMappings. Dostępna również jest opcja HbmMappings, która pozwoli na używanie klasycznych metadanych w pliku XML oraz opcja AutoMappings. AutoMapping wzbudza chyba największy efekt WOW, dlatego że opcja ta eliminuje konieczność definiowania metadanych, dzięki czemu nasza praca jest w pełni DRY(Don’t Repeat Yourself). Rozwiązanie to oparte jest o generację matadanych na podstawie klas domenowych i zdefiniowanych konwencji. Fluent NHibernate wyposażony jest w szeroką gamę domyślnych konwencji, które możemy w prosty sposób nadpisywać.

Ciekawą opcją jest możliwość korzystania z wszystkich metod mapowania jednocześnie. Dzięki temu jesteśmy w stanie sięgnąć po pomoc do pilku XML w przypadku gdy zabraknie nam pewnej opcji w FluentMappings lub AutoMappings. Możliwe to wszystko jest dlatego, że FluentNHibernate działa na zasadzie generowania XML’a z metdanych zdefiniowanych poprzez FluentMappings i AutoMappings. Fakt ten tłumaczy też dlaczego Fluent NHibernate nie wspiera metadanych wyrażonych w postaci atrybutów.

8 comments Marzec 18, 2009

Czy ty też nadużywasz procedur składowanych?

Procedury SkładowaneDerik Whittaker na swoim blogu poruszył bardzo ważną kwestię związaną z nadużywaniem procedur składowanych w celu pisania logiki biznesowej. Jako, że należę do zwolenników programowania obiektowego, całkowicie się zgadzam ze zdaniem Derika. W moim ówczesnym jak i w wcześniejszych projektach, często spotykałem się z nadużyciem procedur składowanych do pisania logiki biznesowej, dlatego też jestem świadomy wielu wad takiego podejścia. Podobnie jak Derrik Whittaker, napotkałem się z argumentacją, że procedurę składowaną można szybko zastąpić bez zbędnego ponownego „deployowania” aplikacji. Moim zdaniem nigdy nie powinno dojść do takiej sytuacji dlatego, że podmienianie funkcjonalności „na żywca” wiąże się ze zbyt wielkim ryzykiem. Jaką możemy mieć pewność, że dana zmiana będzie działać? Sytuacje takie najczęściej kojarzą mi się ze scenami z filmów, w których główny bohater przecina kabelki bomby. W rzeczywistości nie jest tak jak w filmach, gdzie wszystko się kończy szczęśliwie z amerykańską flagą w tle:).

Uważam, że częstym powodem podmiany procedur składowanych na środowisku produkcyjnej jest metodologia FDD(Fear Driven Development), w której każdy się boi cokolwiek ruszyć, dlatego, że szybko może doprowadzić to do „wybuchu”. W związku z tym ponowne deployowanie aplikacji z poprawionym błędem wydaje się większym ryzykiem niż podmiana procedury. Najszybszym rozwiązaniem tego rodzaju problemów jest stworzenie skryptów pozwalających zautomatyzować całkowicie proces deploymentu. Jeżeli posiadacie w projekcie dokument z listą koniecznych kroków, do której sięgacie przy każdym deploymencie, to jest to pierwszy poważny znak na to, że nadszedł czas na poprawienie lub stworzenia skryptów. Same skrypty to tylko początek sukcesu. Kolejnym ważnym elementem są automatyczne testy, które muszą się poprawnie wykonać zanim aplikacja zostanie zdeployowana. Zakładając, że dany projekt korzysta z serwera Continues Integration, tego rodzaju zapewnienie nie stwarza żadnego problemu.

Kolejnym argumentem często używanym przez zwolenników procedur składowanych jest wydajność. Trudno czasami zaprzeczyć temu faktowi, dlatego, że wywołanie jednej procedury zawierającej obszerną logikę biznesową to wyłącznie tylko jedno zdalne zapytanie, które musi dokonać nasza aplikacja. W przypadku zaś, gdy nasza operacja biznesowa dokonywana będzie po stronie aplikacji i wymagać będzie ona stworzenia wielu obiektów zapisywanych w bazie danych, jesteśmy zmuszeni do wielu zdalnych zapytań, a to prowadzi z kolei do problemów wydajnościowych. Dlatego też warto skorzystać z dostępnych mapperów obiektowo relacyjnych, z których niektóre są w stanie wywołać wiele kwerend w ramach jednego zapytania (operacje wsadowe). Należy zwrócić uwagę, że to rozwiązanie nie jest złotym środkiem na wszystko. Jednym z przypadków nadużycia jest tworzenie procesu ETL (Extract Transform Load) przy wykorzystaniu mappera dlatego, że wydajność będzie znikoma w porównaniu do wykorzystywania procedur składowanych lub narzędzi przewidzianych do tego rodzaju procesów takich jak na przykład SSIS, a w przypadku tego typu procesów wydajność odgrywa bardzo ważną rolę.

Jeżeli piszecie logikę w procedurach składowanych i dla operacji  innych niż wsadowych ponieważ uważacie, że jest to bardziej wydajne rozwiązanie, zadajcie sobie proste pytanie: Jak często dokonujemy zmian w kodzie? Często jest tak, że zmieniają się wymagania biznesowe, w takich przypadkach klient oczekuje by dana funkcjonalność została jak najszybciej zmieniona i mu udostępniona. Wydajność w tym momencie nie jest kluczowym elementem. Zresztą moim zdaniem często wydajność w aplikacjach biznesowych jest ważna tylko w przypadku, gdy już natrafiamy na problemy z jej brakiem, wówczas to jesteśmy zmuszeni do zlokalizowania wąskiego gardło.

Powracając do problemu ze zmianami. Jak sama nazwa wskazuje procedury składowane są pisane w postaci kodu proceduralnego, co niesie ze sobą wiele ograniczeń w przypadku realizacji zaawansowanej logiki. W dzisiejszych czasach if i else nie wystarcza, dlatego też stosujemy języki obiektowe. Moim zdaniem brak możliwości programowania obiektowego w procedurach składowanych wskazuje na to, że twórcy języka SQL nie mieli zamiaru by język ten służył do programowania skomplikowanej logiki. Poza tym, który z Was jest wstanie zrozumieć w ciągu 5 min zapytanie z więcej niż pięcioma joinami i pod zapytaniem w warunku? Jak tego rodzaju argumenty nie przemawiają do was, to znaczy, że nigdy nie napotkaliście się na procedurę składowaną, która miała więcej niż 100 linii. Kolejnym dużym problemem jest brak możliwości refaktoryzacji kodu procedur składowanych przy pomocy dostępnych narzędzi developerskich. Nie wspominam już o testach jednostkowych. Choć w tym zakresie pojawiło się parę produktów umożliwiających testowanie, co nie znaczy, że zarządzanie tego typu testami jest proste.

Jednym z ciekawych argumentów, z którymi się również spotkałem była prostota wykorzystywania procedur składowanych do integracji wielu aplikacji. Rozwiązanie to ma na celu wyeliminowanie duplikacji kodu, ale jako, że każda z aplikacji jest tworzona najczęściej przez oddzielne zespoły, rozwiązanie to wręcz prowadzi do tworzenia wielu identycznych procedur. W przypadku, gdy dwie aplikacje będą korzystać z tej samej procedury, jaką mamy pewność, że wszelkiego rodzaju zmiany w procedurze konieczna dla jednej aplikacji nie spowoduje błędu w tej drugiej? Każda aplikacja powinna posiadać swój własny ograniczony kontekst, którego nie ma sensu reprodukować w innej aplikacji. Dlatego też nie powinno dochodzić do potrzeby wywoływania tej samej funkcjonalności przez dwie różne aplikacje z poziomu procedury składowanej. Stosowanie współdzielonej bazy danych w dzisiejszych czasach – opanowanych przez architekturę SOA – jest kruche, dlatego warto zastosować integrację na poziomie funkcyjnym.

Mam nadzieję, że argumenty te przekonały Was do rozważnego zastanowienia się nad tym, czy procedura składowana jest odpowiednim rozwiązaniem na Wasze potrzeby, zanim ją użyjecie. Zachęcam również do wyrażenie swojego zdania na ten temat.

10 comments Marzec 5, 2009


Aktualnie czytam

  • Enterprise Integration Patterns

Tagi

About Agile ALT.NET ASP.NET MVC DDD Domain Driven Design Domain Model Exceptions Front Controller Logika biznesowa NHibernate OOP ORM Pair Programming Podstawy Prezentacja Projekty Informatyczne Routing SQL TDD Wroc.NET Wzorce XP

Dodatki

Blogroll

Znajomi

Najnowsze komentarze

zajefajnyx on Czy ty też nadużywasz procedur…
jenrom on Logowanie i obsługa wyjątków …
am on Fluent Nhibernate Rocks!!…
Jarek on Logowanie i obsługa wyjątków …
jenrom on Logowanie i obsługa wyjątków …

Archiwa