Posts filed under 'Uncategorized'
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

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. 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
Fluent Nhibernate Rocks!!!
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.

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
{
cp.Map(creditCard => creditCard.HolderName);
cp.Map(creditCard => creditCard.Expires);
cp.Map(creditCard => creditCard.CreditCardNumber);
});
JoinedSubClass
{
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


