Logowanie i obsługa wyjątków – czyli jak łatwo można sobie utrudniać życie

Witam ponownie! Przez ostatni czas czułem pewne pohamowanie do pisania bloga, ale dziś stwierdziłem, że nadszedł w końcu czas na przełamanie tej stagnacji. A stało się tak, dlatego, że kolejny (już chyba 1000) raz napotkałem się na bezsensowne rozwiązanie w kodzie. Rozwiązanie to dotyczy przechwytywania wyjątków w celu ich zalogowania. Na pewno każdy z was się spotkał z tego typu kodem, który wygląda podobnie do tego poniżej:

try
{
DoSomething();
}
catch (Exception ex)
{
log.Error(ex.Message);
throw new Exception(“DoSomething failed”);
}

Co w nim jest tak złego, że aż skusiłem się napisać na ten temat?

Krótko mówiąc wszystko oprócz wywołania metody DoSomething

Po pierwsze: Nie ma najmniejszego sensu, by przechwytywać przy każdym wywołaniu kodu wszelki możliwy wyjątek po to, by zalogować jego wystąpienie, a następnie ponownie wyrzucić ten lub inny wyjątek. Dlaczego nie ma to sensu? Odpowiedź jest prosta. Jedyne uzasadnione przechwytywania wyjątków jest wówczas, gdy musimy zareagować na dany wyjątek wywołaniem odpowiedniej logiki. Logowanie wyjątków jak najbardziej zalicza się do tego typu sytuacji, przy czym najczęściej występuje to na poziomie całej aplikacji, gdzie po zalogowaniu wyjątku zapytanie klienta powodujące wyjątek zostanie zignorowane lub praca aplikacji zostanie przerwana. Oczywiście w obu przypadkach konieczne jest powiadomienie klienta o wystąpieniu błędu.

Po drugie: Zalogowanie tylko i wyłącznie wiadomości (linia 3 – właściwość Message) występującego wyjątku w większości przypadków nie wystarcza. Metoda DoSomething może wywoływać inne metody lub posiadać skomplikowane operacje, które z wielu przyczyn mogą się nie powieść. Skąd w takiej sytuacji dowiemy się, co dokładnie mogło być przyczyną wyrzuconego wyjątku? Oczywiście możemy liczyć na to, że na podstawie wiadomości wyjątku dojdziemy do źródła problemy, ale z mojego doświadczenia wychodzi na to, że to jest raczej rzadki przypadek. W takim razie, co powinniśmy jeszcze zalogować po to by uzyskać więcej szczegółowych informacji? StackTrace! Dopiero StackTrace pozwoli nam zlokalizować linię, , która w kodzie spowodowała wyjątek. Dzięki temu jesteśmy w stanie w szybki sposób dojść do rzeczywistej przyczyny błędu. Oprócz StackTrace’u należy również zalogować informacje o wewnętrznym wyjątku (InnerException) jeżeli taki wystąpił. Na ten temat będzie więcej za chwilę. Ze względu na to, że logowanie powyżej wymienionych rzeczy wydaje się bardzo oczywiste, ktoś mądry w firmie Microsoft wpadł na to, że metoda ToString obiektu Exception zwróci wszystkie tego typu informacje. Co prowadzi to tego, że metoda ToString w całości wygląda mniej więcej tak:


Implementacja metody ToString klasy Exception

Implementacja metody ToString klasy Exception


Po trzecie: Przechwytywanie wyjątku po to by wyrzucić nowy wyjątek innego typu ma sens. Przy czym typ nowo wyrzucanego wyjątku powinien jednoznacznie sygnalizować rodzaj problemu. W wyżej przedstawionym kodzie tak się nie dzieje, dlatego, że jest wyrzucany bazowy typ wyjątku – Exception. W przypadku stosowania opisanej przeze mnie strategii warto się zastanowić nad tym, czy nowo wyrzucany wyjątek uzbroić w InnerException w celu udostępniania bardziej szczegółowych informacji o wyrzucanym wyjątku. W celu zrozumienia tego problemu warto spojrzeć na przykład z życia wzięty.

Korzystając z biblioteki Common Service Locator izolujemy pisany przez nas kod od implementacji specyficznego kontenera Inversion of Control. W celu pozyskania przy pomocy ServiceLocator’a obiektu żądanego typu – przykładowo obiekt typu ApplicationController - wystarczy wywołać poniżej przedstawiony kod.

ServiceLocator.Current.GetInstance();

Wywołanie tego kodu powoduje przekierowanie zapytania do konkretnego kontenera IoC. Jako, że domyślnie większość z dostępnych kontenerów IoC wyrzucają wyjątek w momencie, gdy nie są w stanie pozyskać obiekt żądanego typu, nie trudno natrafić na poniżej przedstawiony przypadek wyjątku. Wyjątek ActivateException, jak sama nazwa wskazuje, mówi o tym, że wystąpił problem aktywacji obiektu przy użyciu kontenera IoC. Wyjątek ten jest specyficzny dla biblioteki CommonServiceLocator, który tak naprawdę został wywołany na wskutek przechwycenia wyjątku wywołanego przez kontener IoC – w tym przypadku StructureMap. Informacje zawarte w tym wyjątku nie wskazują na rodzaj problemu, dopiero informacje wyjątku StructureMap, który został dołączony jako wewnętrzny wyjątek (Inner Exception) pozwalają na to. Rozwiązanie to odciąża nas od przechwytywania wyjątków wyrzucanych przez użyty kontener IoC, dlatego, że jedynym wyjątkiem, którego możemy się spodziewać jest ActivateException biblioteki Common Service Locator. Dzięki temu wprowadzamy luźne sprzężenie pomiędzy kontenerem IoC a naszą aplikacją, nie tracąc możliwości śledzenia przyczyn błędów.


ActivateException]

Wyjątek ActivateException z wyjątkiem wewnętrznym

5 comments Lipiec 23, 2009

Struktura „klasycznej” aplikacji trójwarstwowej

Pisząc ten wpis miałem na celu porównać architekturę opartą o metodykę Domain Driven Design, z tą, która najczęściej jest stosowana przynajmniej w projektach dot netowych, czyli architekturą opartą od Data Driven Design. Ze względu na ogrom informacji, które byłyby zawarte w tym w wpisie, stwierdziłem, że lepiej będzie jeżeli podzielę to na dwa wpisy. Drugi pojawi się wkrótce – obiecuję :) .

Na wstępie chciałbym również zwrócić uwagę na to, że Rafał Barszczewski w jednym ze swoich wpisów na swoim blogu poruszył podobny temat. Mam nadzieję, iż poniższy tekst będzie uzupełnieniem informacji przedstawionych przez Rafała, a nie ich powieleniem.

Zakładam, że każdy z Was jest świadom tego, że pisanie całej aplikacji w Code Behind nie doprowadzi do wielkich sukcesów projektu. Jeżeli jesteście innego zdania, nie czytajcie tego wpisu, bo prawdopodobnie nie macie do czynienia z aplikacjami biznesowymi.

Jednym z najbardziej znanych pojęć w świecie architektury oprogramowania jest luźne sprzężenie. Czy nie każdy z nas zastanawiał się nad tym, dlaczego luźne sprzężenie jest tak ważne? W każdym razie ja się zastanawiałem i to tak długo, dopóki nie musiałem zmienić prawie całej aplikacji tylko dlatego, że chciałem wprowadzić proste zmiany. Całe szczęście była to dla mnie nauczka i od tego czasu luźne sprzężenie jest najbardziej krytycznym niefunkcjonalnym wymaganiem w tworzonych przeze mnie aplikacjach.

Po tym jak zdałem sobie sprawę z tego jak ważne jest luźne sprzężenie, zacząłem się zastanawiać nad tym, co zmienić w moim podejściu do pisania kodu tak, bym nigdy więcej nie natrafił na podobne problemy. Dzisiaj już wiem, że nie ma na to złotego środka i wszystko zależy od rozważanego przypadku. Mimo to uważam, że jest pewne rozwiązanie, które jest jednym z pierwszych kroków w celu uzyskania mniej sprzężonej architektury aplikacji. Rozwiązaniem tym jest podział aplikacji na warstwy logiczne.

Większość z Was zapewne zna pojęcie warstw logicznych, ale założę się, że nie każdy jest świadom, czym one tak naprawdę są. Postaram się szybko to wytłumaczyć.

Warstwy logiczne są:

  • Drugim najbardziej używanym pojęciem po luźnym sprzężeniu, jeżeli chodzi o architekturę oprogramowania:).
  • Wzorcem architektonicznym pozwalającym aplikację podzielić na wiele grup podzadań (warstwy), z których każda grupa reprezentuję odpowiedni poziom abstrakcji. Grupy te mogą, a wręcz powinny, być od siebie zależne, przy czym zależności te muszą być jednokierunkowe. Poprzez jednokierunkową zależność należy rozumieć to, że grupa podzadań A, która jest zależna od grupy B, nie powinna być wykorzystywana przez grupę B. Grupa podzadań B nawet nie powinna mieć żadnego rodzaju świadomości o grupie A. Jedyne zależności jakie może mieć grupa B, to te do grup, które w hierarchii są poniżej jej.

     

Jeżeli chodzi o warstwy logiczne, to najczęściej znane są one ze względu na trójwarstwową architekturę aplikacji. Architektura ta zakłada, że aplikacja składa się z warstwy prezentacji zależnej od warstwy logiki biznesowej, która z kolei jest zależna od warstwy dostępu do danych.

Architektura trójwarstwowa w dzisiejszych czasach jest na tyle wszechobecna, że niemalże każdy projekt biznesowy, z którym miałem do czynienia z niej korzystał. Co nie znaczy, że jest ona złotym środkiem na wszystko. Głównym problemem związanym z tym podejściem jest to, że prowadzi ono często do tego, że każda warstwa reprezentuje jeden typ klasy.

  • Warstwa prezentacji składa się wyłącznie z formularzy (Web, Desktop, etc.), w których zawarta jest logika prezentacji(Code Behind)
  • Warstwa logiki biznesowej składa się z klas posiadających metody, za którymi kryje się odpowiednia logika biznesową. Metody te są najczęściej wywoływane bezpośrednio z poziomu warstwy prezentacji. Tego typu rozwiązanie znane jest jako wzorzec Transaction Script.
  • Warstwa dostępu do danych zarządza dostępem do źródła danych. Najczęściej źródłem jest baza danych, ale zdarzają się również inne przypadki takie jak plik, czy inne aplikacje. W przypadku bazy danych, warstwa ta najczęściej się składa z metod, które wywołują odpowiednie zapytania SQL lub procedury składowane. Dzięki wprowadzeniu tego typu klas, jesteśmy w stanie wprowadzić luźne sprzężenie pomiędzy logiką biznesową aplikacji a źródłem danych, które aplikacja wykorzystuje.

W tym powyższym podejściu brakuje mi jednak jednej rzeczy, mianowicie obiektów domenowych. Obiekty te najczęściej znajdują swoje miejsce w architekturze trójwarstwowej, ale trudno je przyporządkować do konkretnej warstwy. Teoretycznie powinny być traktowane jako część warstwy logiki biznesowej. Jednak nie jest tak dlatego, że są one współdzielone przez wszystkie warstwy i służą jedynie jako medium komunikacji pomiędzy tymi warstwami. Należy zwrócić uwagę, że obiekty te posiadają jedynie stan (właściwości), a to znaczy, że są one typowym przykładem anemicznych obiektów domenowych.

Żeby całość lepiej zrozumieć posłużę się przykładem.

Załóżmy, że mamy aplikację, w której użytkownik posiada dostęp do przeglądania określonych zasobów. W celu przejrzenia zasobu wysyła zapytanie wraz z informacją identyfikującą określony zasób. Zapytanie to jest przechwytywane przez warstwę prezentacji i zostaje przekierowane do warstwy logiki biznesowej, gdzie z kolei jest sprawdzane, czy dany użytkownik może dokonać tego typu operacji. Następnie zostaje wykonywane zapytanie do warstwy dostępu do danych, w celu zwrócenia informacji o określonym zasobie. Zapytanie te tłumaczone jest na przykład na język SQL i zostaje wykonane zapytanie do bazy danych. Zwrócone informacje z bazy tłumaczone są do postaci obiektu domenowego, który zostaje zwrócony z warstwy dostępu do danych do warstwy logiki biznesowej. Z kolei ta sprawdza czy dany użytkownik, powinien mieć dostęp do tego obiektu domenowego. W przypadku gdy wszystko będzie się zgadzało, obiekt ten zostanie przekazany do warstwy prezentacji, w ramach której wygenerowany zostanie odpowiedni widok przedstawiający informacje zawarte w tym obiekcie domenowym.

Wszystko jest “fajne”, ale czy tego typu architektura aplikacji nadal powinna być nazywana trójwarstwową?

Architektura ta jest wygodna w przypadku prostych aplikacji, zwanych często “Data Driven”, dlatego, że aplikacje te nie posiadają skomplikowanych reguł biznesowych. Głównym problemem wyżej przedstawionej architektury jest duplikacja kodu z logiką biznesową, dlatego że stosowanie Transaction Script zmusza nas do definiowania osobnego kodu dla każdej poszczególnej operacji. Dlatego też w przypadku, gdy celem aplikacji jest realizacja wielu skomplikowanych reguł biznesowych, należy się zastanowić nad architekturą zgodną z założeniami Domain Driven Design.

3 comments Kwiecień 28, 2009

Prezentacja – “Test Driven Development – Dlaczego warto?” @ wroc.net

źródło: http://blog.briandicroce.com/wp-content/uploads/2008/03/image50.png

źródło: http://blog.briandicroce.com/wp-content/uploads/2008/03/image50.png

Czwartek 02 kwietnia 2009 o godz 19:00 będę miał przyjemność wygłosić prezentację na temat Test Driven Development w ramach spotkania wrocławskiej grupy .Net.

Prezentacja ta będzie poświęcona przede wszystkim wprowadzeniu do metodyki Test Driven Development, koncentrując się na jej zaletach i wskazówkach potrzebnych do rozpoczęcia z nią przygody. Poza częścią teoretyczną zostanie również przedstawiony sposób implementacji przykładowej aplikacji przy zastosowaniu Test Driven Development.

Rejestracja na to spotkanie odbywa się poprzez serwis Goldenline.

Dodatkowy informacje można znaleźć na stronie wrocławskiej grupy .Net.

2 comments Marzec 25, 2009

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

Previous Posts


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