Posts tagged NHibernate
Jak zwątpiłem w transakcje
Jun 17th
Transakcje to fajna sprawa. Polubiłem je od pierwszego użycia. Zostałem oczarowany przez ich magiczną właściwość — zwalniają z myślenia o spójności danych. Cool, przecież nie lubię myśleć. Jeszcze bardziej byłem oczarowany, gdy odkryłem transakcje rozproszone. To dopiero jazda. Mogę coś “zapdejtować” na tej bazie, na tej drugiej bazie i jeszcze wrzucić komunikat do kolejki MSMQ i wszystko wykona się transakcyjnie — w całości lub wcale.
Od dłuższego czasu zaczynam jednak wątpić w transakcje, szczególnie te rozproszone. Zbyt często ja sam lub ktoś ze znajomych wpada przez nie w pułapkę bez wyjścia. Zamiast używać najodpowiedniejszej technologii, musimy wtedy wziąć taką, która współdziała z naszym sposobem zarządzania transakcjami.
Najczęstszym problemem, na który natrafiałem jest odbieranie i wysyłanie komunikatów do kolejek z jednoczesnym zapisem danych w bazie. Pierwsza rzecz, która przychodzi w takim wypadku do głowy, to transakcja, która obejmuje zarówno infrastrukturę kolejkową, jak i RDBMS. Pierwsza nie oznacza, niestety, najlepsza.
Nieodpowiednia technologia
Wymaganie transakcyjności w komunikacji z RDBMS i kolejkami pchnęło mnie kilka lat temu do wykorzystania SQL Server Service Broker jako kolejki komunikatów. Unikalną cechą Service Broker’a jest fakt, że żyjąc wewnątrz silnika bazodanowego, jest w naturalny sposób zintegrowany z mechanizmami transakcyjnymi SQL Server. Nie ma potrzeby stosowania (wolnych) transakcji rozproszonych, aby wysłać komunikat i zrobić przysłowiowy “apdejt”.
Niestety poza tą jedną zaletą, Service Broker ma bardzo wiele wad, z których największą jest brak dobrego gotowego API w C#, o modelu usługowym (np. WCF) nie wspominając. Jest bardzo trudny w użyciu i utrzymywaniu. Definitywnie był to zły wybór technologiczny, podyktowany jedynie chęcią zastosowania mechanizmu transakcji.
Niezgodność API
W każdym nietrywialnym systemie wykorzystuje się wiele zewnętrznych bibliotek. Bardzo rzadko pochodzą one od jednego dostawcy. Zdecydowanie częściej jest to mieszanka rozwiązań open source oraz COTS. Problem pojawia się, gdy dwie z bibliotek mają współpracować w ramach jednej transakcji. Weźmy jako przykład NHibernate i NServiceBus. Ten pierwszy posiada własną abstrakcję reprezentującą transakcje, podczas gdy pod spodem korzysta z transakcji ADO.NET. Ten drugi wykorzystuje transakcje System.Transactions do dostępu do MSMQ.
W przypadku użycia obu technologii w jednej transakcji, pojawia się problem, jak sprawić, aby każda z bibliotek mogła wykorzystywać swoje API odwołując się do tej samej fizycznej transakcji. W wypadku wspomnianej pary, jedynym rozwiązaniem jest pozwolić NHibernate używać zewnętrznych transakcji System.Transactions. Jest to jednak zamiana jednego problemu na inny. Do niedawna bowiem pojawiał się w NServiceBus wyciek pamięci, ponieważ zachowanie NHibernate w wypadku współpracy z System.Transactions jest bardzo słabo udokumentowane i łatwo o błąd wynikający z niezrozumienia.
Błedne implementacje transakcyjności
Jonathan Oliver opisał swoje testy kompatybilności różnych silników bazodanowych z transakcjami rozproszonymi na swoim blogu. Wnioski nie są optymistyczne: jedynie sterowniki do SQL Server i Oracle w pełni i bez problemów je obsługują.
Z drugiej strony nie dziwie się, że mniej płatne lub darmowe rozwiązania nie wspierają rozproszonych transakcji. Zdecydowana większość systemów radzi sobie bez nich, więc zysk (w sensie pieniędzy z licencji lub satysfakcji użytkowników) z implementacji wsparcia dla nich byłby znikomy.
Proste rozwiązanie
Rozwiązanie jest oczywiście proste. Wymaga jednak nieco innego podejścia do projektowania komunikacji. Wystarczy zadbać o to, aby każdy komunikat był
- albo idempotentny (wielokrotne przetworzenie takiego komunikatu daje taki sam efekt, jak przetworzenie jednokrotne),
- albo jednoznacznie identyfikowalny (unikalne ID).
Drugi przypadek można sprowadzić do pierwszego dodając rejestr przetworzonych komunikatów zawierający ich unikalne ID i przed obsłużeniem komunikatu sprawdzać, czy aby nie został przetworzony wcześniej.
Po spełnieniu któregoś z powyższych warunków zyskujemy możliwość rozłącznego zarządzania transakcją związaną z obieraniem komunikatu (MSMQ, ServiceBroker) oraz transakcją bazodanową. Ta pierwsza powinna być zatwierdzana dopiero po zatwierdzeniu tej drugiej. Powoduje to, że mamy pewność, iż każdy komunikat zostanie przetworzony co najmniej raz. Z drugiej strony idempotentność gwarantuje nam, że skutki wielokrotnego przetworzenia będą takie, jak jednokrotnego. Ostatecznie więc uzyskujemy semantykę dokładnie raz — taką samą jak przy zastosowaniu transakcji rozproszonych.
Praktyka
Dokładnie taki mechanizm zastosowałem w swoim ostatnim systemie. Pozwolił mi on na użycie klienta Service Broker (użycie tej kolejki było narzucone z góry) w połączeniu z NHibernate bez konieczności integracji obu technologii. Ponieważ transakcje były rozdzielone, klient kolejek nie musiał wiedzieć nic o dostępie do danych i vice versa. Prawdopodobnie zaoszczędziło mi to kilka dni pracy przy implementacji, testach i poprawianiu bugów w warstwie integracyjnej. Nauczyłem się także, że najlepszą strategią integracji technologii jest unikanie integracji technologii, kiedy to tylko możliwe.
Entity Framework 4 a NHibernate
May 25th
W minioną sobotę miałem przyjemność uczestniczyć w krakowskiej edycji Visual Studio Community Launch. Co prawda, jako jeden z organizatorów, nie jestem najlepszą osobą do obiektywnej oceny tego wydarzenia, ale moim zdaniem było super.
Podczas VSCL miałem okazję sprawdzić się także w roli prelegenta, prowadząc dwie piętnastominutowe mikroprezentacje dotyczące Entity Framework 4 oraz Windows Communication Foundation 4. Przykłady kodu dla obu prezentacji umieściłem na MSDN Code Gallery odpowiednio tutaj i tutaj. Zapewne duża część z Was nie była na konferencji, dlatego postanowiłem owe przykłady omówić tutaj, na blogu. Dziś – EF4.
W obu przypadkach starałem się wpleść prezentację nowych funkcji danej technologii w jakąś większą historię. Dla Entity Framework tą większą historią jest możliwość współdzielenia (części) modelu z aplikacją napisaną w NHibernate.
Design
Solution wygląda tak:
Znajdują się w nim dwie aplikacje: obsługa zamówień (Orders) oraz bank (Accounts). Obie aplikacje składają się z prostego programu demonstrującego działanie, modelu domeny (Model) oraz obsługi persystencji (DataAccess). Współdzielony fragment to Parties – projekt niezależny od technologii persystencji (bez referencji do NHibernate lub EF) zawierający implementację podmiotów (osób fizycznych i organizacji).
POCO
Jednym z celów nowej wersji Entity Framework było wsparcie dla budowy modeli POCO (Plain Old CLR Object). Dlaczego jest to takie ważne? Ponieważ właśnie obsługa POCO pozwala wykorzystywać w EF klasy, które nie były napisane konkretnie z myślą o nim oraz, patrząc z drugiej strony, pozwala wykorzystać klasy napisane specjalnie dla EF w innych kontekstach. POCO daje nam komfort braku zależności od frameworku ORM na poziomie modelu domeny – coś, czego nie sposób przecenić. Dla przykładu, klasa Party reprezentująca podmiot wygląda tak:
public class Party
{
public virtual int Id { get; protected set; }
public virtual Address Address { get; protected set; }
protected Party(Address address)
{
Address = address;
}
protected Party()
{
}
}
Lazy Loading
Leniwe ładowanie (lazy loading) jest kolejnym wielkim nieobecnym z pierwszej wersji EF, który doczekał się implementacji w wersji drugiej (czwartej). Jest ono jednak domyślnie wyłączone. Aby je włączyć, należy dodać do konstruktora kontekstu następującą linię:
ContextOptions.LazyLoadingEnabled = true;
Wszystko jednak ma swoją cenę. Ceną, którą płacimy za obsługę leniwego ładowania w EF jest konieczność „wirtualizacji” wszystkich property klas modelu. Jest to niezbędne, ponieważ EF w trakcie działania systemu generuje dynamicznie klasę dziedziczącą po naszej. Nie jest to jednak wielki problem, ponieważ NHibernate ma analogiczne wymaganie. Możemy więc spokojnie przywyknąć do myśli, że standardem dla ORM jest konieczność deklarowania wirtualnych property.
Value Objects
Value Objects są nazywane w EF „Complex Type”. Ot, taka fanaberia ludzi z Microsoft. Complex Type w EF może być modyfikowalny. Ja osobiście jednak odradzałbym to ze wszystkich. Niemodyfikowalność Value Object jest bardzo ważną cechą, m.in. ułatwiającą testowanie.
Ponieważ dla Complex Type EF nie prowadzi change tracking’u (sprawdzenie, czy należy zaktualizować bazę wykonywane jest za pomocą porównania wartości aktualnych i pobranych), nie ma sensu wirtualizacja properties CT. Znowu, jest to zgodne z tym, jak ja pracuje z NHibernate. Moje Value Objects muszą być jak najbardziej niezależne kontekstu bazodanowego. Jedyne na co się mogę (i niestety muszę) zgodzić to pusty konstruktor z widocznością protected. Zarówno EF, jaki NHibernate go wymagają. Efektem tego zestawu wymagań jest klasa Address będąca Value Objectem reprezentującym adres podmiotu:
public class Address
{
public string Street { get; private set; }
public string BuildingNumber { get; private set; }
public string City { get; private set; }
public string Country { get; private set; }
protected Address()
{
}
public Address(string street, string buildingNumber, string city, string country)
{
Street = street;
BuildingNumber = buildingNumber;
City = city;
Country = country;
}
}
Enkapsulacja
Enkapsulacja to zawsze dobra rzecz. Nie inaczej jest w wypadku modelu domeny. Upublicznianie getterów (a broń Boże setterów) właściwości jest prostą drogą do degradacji naszego modelu do roli prostych struktur danych. A przecież nie o to nam wszystkim chodzi. Z tego powodu bardzo się zmartwiłem po przeczytaniu na MSDN opisu wymagań dla POCO w EF. Wynika z niego (chyba, że ja źle rozumiem), że aby leniwe ładowanie działało, wszystkie właściwości klasy muszą być „public virtual”.
Na szczęście przeglądając sieć natknąłem się na wzmiankę, iż wystarczy „protected virtual”. Postanowiłem sprawdzić. Jakaż była moja radość, gdy okazało się, że wszystko działa. W kontekście enkapsulacji EF4 ma analogiczne wymagania, co NHibernate. Yupi:-)
Klasa Account jest świetnym przykładem enkapsulacji dla kolekcji: lista operacji związanych z kontem nie może być modyfikowana bezpośrednio, ale jedynie poprzez wywołania Credit lub Debit:
public class Account
{
//...
public decimal Balance { get; protected set; }
protected virtual IList<Operation> Operations { get; set; }
//...
public virtual void Debit(decimal amount, string title)
{
if (Balance - amount < 0)
{
throw new InvalidOperationException();
}
var op = new Operation(-amount, title);
Operations.Add(op);
Balance -= amount;
}
public virtual void Credit(decimal amount, string title)
{
var op = new Operation(amount, title);
Operations.Add(op);
Balance += amount;
}
}
Podsumowanie
Druga edycja Entity Framework jest o niebo lepsza od poprzedniej. Właściwie mogę powiedzieć z czystym sumieniem, że jest nawet używalna w kontekście Domain-Driven Design. Cieszy mnie to niezmiernie, bo lubię mieć wybór. Pluralizm to dobra rzecz. Czy rozważam przesiadkę z NHibernate na EF? Na pewno nie w tej wersji. Przewaga NH w przypadku bardziej skomplikowanych kwestii jest jeszcze zbyt duża. Z drugiej strony nie potrzebuję (i nie będę potrzebował) wszystkich tych kwestii związanych z obsługą architektur n-tier, w które Microsoft pakuje tyle pary.
Jeśli jednak EF będzie się rozwijać w tak szybkim tempie, jest wysoce prawdopodobne, że wersja 6.0 będzie stanowić groźną konkurencje dla NHibernate także w kontekście DDD.
Automatic domain model persistence – halfway through
Apr 30th
Just a quick note. There was no new post yesterday because I was busy implementing automatic persistence for domain model concept-proof. In the previous post I was writing about private field automapping. Since then I managed to implement a bunch of other concepts, including composite elements.
My fork of FluentNHibernate (automapping-fields branch) on github was updated with the most current code version.
DDDSample.NET ‘AutoPersistence’ branch uses the modified FNH to persists its domain model and it works! Without a single mapping file!
So, if everything works fine, why ‘halfway through’? That is because the code is very, very dirty and needs much work before it can be used in production.
Using FluentNHibernate to automaticaly persist your domain model
Apr 26th
Yesterday I came across the idea of totally automating domain persistence. There are out there plenty of convention-based persistence frameworks for ActiveRecord (like the Ruby on Rails one), but there is no equivalent solution for the Domain Model (as defined by Eric Evans Blue Book).
Your first thought may be that it is because domain model mapping is complicated. In fact many folks who write about Domain Model point the complexity of mapping as a key difference between DM and AR — the latter is supposed to be limited to one-to-one table to class mapping.
I disagree. In my opinion the difference lies in set of conventions and patterns used to create AR or DM models. While AR uses classes, public properties and relations, DM has its own: entities, value objects, aggregates and so on. The complexity level is comparable when sticking to DM patterns which greatly limit the set of mapping choices. This is the path I want to go.
So, my initiative is to implement Domain Model’s conventions and patterns using FluentNHibernate. I want my persistence mapping be generated straight from domain model without any external help. I want to define my conventions which will limit the possibilities so that automatic mapping is possible. In order to do this I will have to add some metadata to the model, which itself is a nice idea because it will make my models more expressive.
Every journey starts with a first step. I started with the following requirement/story
Private (and possibly readonly) fields can be automatically mapped if there is a corresponding read-only property.
This is not supported out-of-the-box by FluentNHibernate, so I had to fork it and change it. FNH uses a set of rules to map all the features of persistent class. These rules (IAutoMapper implementations) are governed by AutoMapper class. My modification extends the property mapping rule (AutoMapProperty class) so that is uses a set of access strategies to infer the access method of particular property instead of hard-coding property access. Currently (as it is a proof-of-concept) the only supported strategies are Property (formerly built-in) and CamelCase. You can download the modified version of FNH from here (GitHub).
I will be updating a DDDSample.Net branch (I have to create a new one;-)) with results of my work. The next step would be probably relations between aggregates and within an aggregate.
Oswajanie Fluent NHibernate
Apr 22nd
O FluentNHibernate napisano już całkiem sporo, jednak niestety duża część z informacji krążących po Sieci jest już nieaktualna z powodu zmian w API. Postanowiłem więc podzielić się z Wami wnioskami z moich wczorajszych zmagań z FNH. Zanim jednak przejdę do konkretów, jeśli ktoś nigdy nie używał tej biblioteki, prawdopodobnie powinien zacząć o tych postów Procenta.
Enumy
Jak Procent zauważył, mapowanie enumów za pomocą właściwego im typu całkowitoliczbowego realizuje się za pomocą klauzuli CustomType:
Map(x => x.Gender).CustomType<Gender>();
Jako leniwy programista chciałbym jednak, aby FluentNHibernate zrobił to za mnie. Nauka FNH odbywa się za pomocą konwencji. Jedyne, co należy stworzyć to własną konwencję mapowania typów enum. Oto ona:
public class EnumAsIntConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
Nie muszę chyba tłumaczyć jak działa, ponieważ wszystko widać na pierwszy rzut oka. Klasę tą znalazłem gdzieś na StackOverflow.
varchar a nvarchar
Nie zamierzam się wdawać w filozoficzne dywagacje na temat przewagi jednego nad drugim (ani drugiego nad pierwszym). Po prostu chciałem, aby moje napisy były przechowywane w bazie jako varchar. W Sieci znalazłem wiele rozwiązań, ale żadne mnie nie satysfakcjonowało, ponieważ nie można go było zautomatyzować za pomocą konwencji. Wtedy, przeglądając listę typów wspieranych przez NHibernate natrafiłem na AnsiString. Pomyślałem, że spróbuję. Tak zrodziła się konwencja
public class StringAsVarcharConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType == typeof(string));
}
public void Apply(IPropertyInstance target)
{
target.CustomType("AnsiString");
}
}
która uczy FNH, aby wszystkie napisy mapował do kolumn varchar o odpowiedniej długości.
varchar(max)
A propos długości, pewnie wiecie, ale na wszelki wypadek: zarówno przy użyciu klasycznych plików hbm.xml, jak i płynnych mapowań, aby wymusić na NHibernate zastosowanie typu varchar(max) (i analogicznego — varbinary(max)) należy podać długość pola większą niż 8000. Ja stosuje 8001 wraz z odpowiednim komentarzem.
Decimal
Kolejna kwestia to standaryzacja reprezentacji kwot w projekcie. W moim wypadku zdecydowaliśmy się na zastosowanie decimal(18,2) na poziomie bazy danych. Poniższa konwencja uczy FluentNHibernate respektowania naszej decyzji:
public class DecimalConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType == typeof(decimal));
}
public void Apply(IPropertyInstance target)
{
target.Scale(2);
target.Precision(18);
}
}
Stereotypy
W przypadku inżynierii oprogramowania są niewątpliwie dobrym zjawiskiem. Jeśli 90% moich pól tekstowych reprezentuje jedną z dwóch kategorii, mogę pokusić się o zdefiniowanie, za pomocą metod rozszerzających, odpowiednich stereotypów.
Map(x => x.SendersReference).AsReference(); Map(x => x.CustomerSpecifiedReference).AsReference(); Map(x => x.BeneficiaryInformation).AsTextualInfo(); Map(x => x.RemittanceInformation).AsTextualInfo();
Ich implementacja wygląda następująco:
public static PropertyPart AsReference(this PropertyPart propertyPart)
{
return propertyPart.Length(16).Not.Nullable();
}
public static PropertyPart AsTextualInfo(this PropertyPart propertyPart)
{
return propertyPart.Length(144).Not.Nullable();
}
Dodatkową korzyścią ze stosowania takich rozszerzeń jest fakt, że nadajemy naszemu mapowaniu sens biznesowy. Zarówno Reference, jak i TextualInfo są pojęciami pochodzącymi z domeny problemu, które są elementem naszego wszędobylskiego języka (ubiquitous language).
Z drugiej (technicznej) strony, na podobnej zasadzie można stworzyć rozszerzenie AsVarcharMax(), które przypisze tę nieszczęsną długość 8001 i ukryje ten kod w jednym miejscu.
Konwencje, konwencje
W żadnym razie nie narzucam Wam moich konwencji. Jeśli Wam odpowiadają, bierzcie i stosujcie. Jeśli nie — stwórzcie własne. Miejcie jednak jakieś. Naprawdę, warto mieć konwencje.




