Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Konkurs Enterprise Library – Exception Handling (EHAB)

W zeszłym tygodniu mieliście okazję przeczytać o Logging Applicatinn Block i poznaliście pierwsze zadanie konkursowe. Dziś kontynuacja konkursu!

Refaktoryzacja do Exception Handling Application Block

W każdej aplikacji, małej i dużej, mogą pojawić się sytuacje wyjątkowe. Prosta operacja dostępu do pliku może się zakończyć kilkoma różnymi problemami. Wyjątki od samego początku tworzenia aplikacji towarzyszą każdemu programiście. W prostych aplikacjach, nie posiadających struktury warstwowej, zarządzanie wyjątkami nie sprawia wielu problemów. Jeżeli jednak mamy do czynienia ze skomplikowanym systemem o strukturze warstwowej, być może rozproszonym, oraz dużą ilością kodu źródłowego to może okazać się, że zadanie to nie jest trywialne. Aplikacja taka będzie wykonywała wiele różnych operacji, które mogą się zakończyć sytuacjami wyjątkowymi. Operacje te będą wykonywane w różnych warstwach systemu. Nie jest też powiedziane, że w każdej warstwie wyjątki mają być traktowane tak samo.

Rozwiązania dla tych wszystkich przypadków dostarcza nam Exception Handling Application Block.

Uzasadnienie

EHAB to jeden z komponentów potężnej biblioteki znanej pod nazwą Enterprise Library. Jest przeznaczony do spójnej i kompleksowej obsługi sytuacji wyjątkowych w dowolnym systemie. Za jego pomocą można obsługiwać wyjątki we wszystkich warstwach architektonicznych aplikacji. Tak jak i inne bloki tak i EHAB posiada jeden scentralizowany punkt (plik konfiguracyjny), w którym można definiować strategie przetwarzania wyjątków dla całej aplikacji, bez konieczności jej ponownej kompilacji.

Definiowanie strategii obsługi wyjątku jest bardzo proste. Podstawowym elementem konstrukcyjnym są tak zwane exception handlers. Wraz z biblioteką dostarczono kilka z nich, które są najczęściej wykorzystywane podczas obsługi błędów:
  • Wrap Handler – pozwala na opakowanie obsługiwanego wyjątku w wyjątek innego typu. Oryginalny wyjątek jest zachowywany w polu      InnerException nowego wyjątku.
  • Replace Handler – zastępuje obsługiwany wyjątek nowym wyjątkiem.
  • Logging Handler – pozwala na zapisanie wszystkich informacji dotyczących wyjątku.
  • Fault Contract Exception Handler – jest przeznaczony do obsługi wyjątków w WCF.
Definiowanie zaczynamy od określenia co najmniej jednej wytycznej (ang. policy) dotyczącej obsługi konkretnych błędów. Zestaw wytycznych definiuje się w zależności od potrzeb. Dla danej wytycznej definiujemy zbiór typów wyjątków, dla których chcemy określić sposób ich obsługi. Następnie dla każdego typu wyjątku definiujemy zestaw (ciąg) exception handlers, które będą wykorzystywane do obsługi danego wyjątku. Należy tu wspomnieć, że exception handlers są łączone w ciąg i wykonywane jeden po drugim, a wynikiem obsługi wyjątku jest wynik uruchomienia ostatniego handlera.

Wywołania EHAB mają zastąpić typowy kod, który pojawia się w blokach catch. Dzięki temu programiści nie muszą powtarzać tego samego kodu (np. logowania informacji o wyjątku) w wielu miejscach systemu. EHAB dostarcza im spójnego mechanizmu pozwalającego na łatwą konfigurację. Przykładowe użycie wygląda następująco:

Kod. 1 – obsługa wyjątku za pomocą EHAB
try
{
    // Run code.
}
catch(Exception ex)
{
    bool rethrow = ExceptionPolicy.HandleException(ex, " Data Access Policy");
    if (rethrow)
        throw;
}


Jak łatwo zauważyć, programista w trakcie tworzenia funkcji nie musi rozróżniać typów wyjątków, które może zgłosić wywoływany fragment kodu. Wystarczy, że wykorzysta pojedynczą klauzulę catch i w niej przekaże obsługę błędu do EHAB, a ten na podstawie konfiguracji odpowiednio obsłuży podany wyjątek.

Sposób wykonania

Refaktoryzacja do EHAB jest w moim odczuciu dość prosta i mechaniczna. W systemie, który będziemy poddawać refaktoryzacji znajdziemy wiele bloków try catch. To one będą głównym punktem naszego zainteresowania. Wprowadzanie EHAB wygląda następująco:
  1. Dodaj odpowiednie referencje do projektu oraz przygotuj plik konfiguracyjny.
  2. Zlokalizuj wszystkie miejsca, w których obsługiwane są wyjątki.
  3. Dla każdego z tych punktów:
    1. Jeżeli potrzeba wprowadź nowe typy wyjątków.
    2. Przenieś definicję obsługi wyjątku do pliku konfiguracyjnego do odpowiedniej wytycznej (policy).
    3. Zastąp wszystkie klauzule catch jedną ogólną klauzulą (kod 1) oraz określ wytyczną, z której należy skorzystać.

Przykład

Przykładowa aplikacja jest symulacją prostego okna logowania użytkownika do systemu. Użytkownik musi wprowadzić swój identyfikator oraz hasło, a następnie wcisnąć przycisk OK. Budowa aplikacji symuluje podział trójwarstwowy: warstwę dostępu do bazy danych, warstwę logiki biznesowej oraz interfejs użytkownika. Kod obsługi wyjątków znajduje się w każdej warstwie, a w warstwie UI, wyjątki zamieniane są na komunikaty dla użytkownika. Należy zauważyć, że wyjątek pojawiający się w warstwie dostępu do bazy danych propagowany jest w niezmienionej formie aż do interfejsu użytkownika.

Takie podejście do obsługi sytuacji wyjątkowych rodzi kilka problemów. Przede wszystkim wyjątki zawierają bardzo dużo informacji o systemie łącznie z np. konfiguracją połączenia z bazą danych, która może zawierać nazwę użytkownika oraz hasło. Zwykły użytkownik aplikacji nie potrzebuje aż tak szczegółowych danych, a w przypadku nazwy użytkownika i hasła dostępu do bazy danych nie powinien mieć do nich dostępu! Takich informacji może potrzebować programista lub osoba utrzymująca system. Dlatego właśnie należy ukrywać takie dane.

Dobrą praktyką jest też przekazywanie wyjątków tylko bezpośrednio do warstwy nadrzędnej, a nie jak to jest zrobione w przykładowej aplikacji z warstwy dostępu do bazy danych prosto do interfejsu użytkownika. Takie podejście pozwala odseparować tworzenie oraz zarządzanie poszczególnymi warstwami. Te dwa problemy rozwiążemy wprowadzając EHAB.

Zdefiniowanie konfiguracji dla EHAB

W pierwszym kroku refaktoryzacji przygotujemy projekt do wprowadzenia EHAB. Najpierw dodamy referencje do wymaganych bibliotek. Pierwszą z nich jest Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll, która jest biblioteką podstawową EHAB i zawiera całą jego  implementację. Druga biblioteka to Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll, która zawiera implementację exception handlers pozwalających zapisywać szczegółowe dane o wyjątkach za pomocą LAB.

W drugim kroku zainicjujemy plik konfiguracyjny aplikacji. Klikamy na nim prawym przyciskiem i wybieramy ‘Edit Enterprise Library Configuration’. Otworzy nam się drzewo konfiguracyjne całego EntLiba. Naszym zadaniem jest dodanie bloku konfiguracyjnego dla EHAB (rys. 1) i jego wstępna konfiguracja.


Rys.1 Dodawanie konfiguracji dla EHAB.

Następnie musimy zdefiniować co najmniej jedną wytyczną (policy). My zdefiniujemy sobie dwie zgodnie z naszym podziałem na warstwy. Będziemy mieli wytyczną dotyczącą wyjątków z warstwy dostępu do bazy danych oraz drugą wytyczną dla logiki biznesowej (rys. 2).


Rys. 2 Wytyczne dla warstwy dostępu do bazy danych oraz logiki biznesowej

Wprowadzenie EHAB

Refaktoryzację zaczniemy od warstwy najniższej, czyli od dostępu do bazy danych. Aby odseparować tą warstwę do warstwy logiki biznesowej wprowadzimy własne typy wyjątków i będziemy je propagować w górę. W przykładowej aplikacji mamy blok catch, który obsługuje wyjątki typu SqlException.

public class UserIdentityDAO
{
    public static UserIdentity GetUserIdentity(string identifier)
    {
        UserIdentity identity = null;
        try
        {
            SqlConnection conn = new SqlConnection(Properties.Settings.Default.UserDatabaseConnectionString);
            conn.Open();
            //Kontynuacja...
        }
        catch (SqlException)
        {
            //Robimy co potrzebujemy, na przykład
            //zapisujemy info o błędzie
            throw;
        }
        return identity;
    }
}


Najpierw zdefiniujemy w tej warstwie nowy typ wyjątku, który odpowiadał będzie błędom dostępu do bazy danych. Nazwijmy go DatabaseException. Definiując nowe wyjątki warto kierować się pewnymi dobrymi praktykami. Ponieważ konstruktory nie są dziedziczone z klasy bazowej należy dodać je ręcznie. Zaleca się definowanie trzech konsruktorów tak, jak w kodzie poniżej. Dodatkowo jeżeli wyjątek ma być przekazywany w wywołaniach zdalnych (remoting) to powinien być oznaczony atrybutem SerializableAttribute i implementować interfejs ISerializable.

public class DatabaseException : Exception
{
    public DatabaseException() : base() { }
    public DatabaseException(String message) : base(message) { }
    public DatabaseException(String message, Exception innerException) : base(message, innerException) { }
}


Wyjątek ten chcemy propagować do warstwy logiki biznesowej za każdym razem, gdy wystąpi błąd dostępu do bazy danych czyli w przypadku wystąpienia wyjątku SqlException. Przekształcamy odpowiednio kod aplikacji: zmieniamy kod obsługi błędu w warstwie dostępu do bazy danych oraz zmieniamy typ wyjątku przechwytywany w wyższych warstwach.

public class UserIdentityDAO
{
    public static UserIdentity GetUserIdentity(string identifier)
    {
        UserIdentity identity = null;
        try
        {
            SqlConnection conn = new SqlConnection(Properties.Settings.Default.UserDatabaseConnectionString);
            conn.Open();
            //Kontynuacja...        }
        catch (SqlException se)
        {
            //Robimy co potrzebujemy, na przykład
            //zapisujemy info o błędzie
            throw new DatabaseException("Błąd dostępu do bazy danych.", se);
        }
        return identity;
    }
}


Należy zauważyć, że tworząc nowy wyjątek chcemy zachować także ten oryginalny. Dlatego też umieszczamy ten oryginalny jako wyjątek wewnętrzny (inner exception) nowego wyjątku. Dzięki temu nie stracimy cennych (z punktu widzenia programisty) informacji, które są na wagę złota w przypadku poszukiwania przyczyny ewentualnego błędu. Po tej operacji kod aplikacji powinien się kompilować i działać tak samo jak przed modyfikacjami.

W drugim kroku mamy za zadanie przenieść obsługę wyjątku z klauzuli catch do pliku konfiguracyjnego. Sposób działania obecnej klauzuli jest następujący:
  1. Przechwytywany jest wyjątek SqlException
  2. Tworzony jest nowy wyjątek, który jest propagowany w górę. Wewnątrz nowego wyjątku znajduje się oryginalny wyjątek.
W EHAB do osiągnięcia takiego zachowania służy Wrap Handler. Zdefiniujmy więc odpowiedni wpis w konfiguracji. Najpierw w wytycznej dla warstwy dostępu do bazy danych zdefiniujmy typ obsługiwanego wyjątku. W naszym przypadku będzie to SqlException. We właściwościach powstałej w ten sposób gałęzi ustawmy PostHandlingAction na ThrowNewException jako, że chcemy zgłosić nowy wyjątek będący wynikiem uruchomienia Wrap Handlera (rys. 3).


Rys. 3 Właściwości obsługiwanego wyjątku typu SqlException.

Następnie definiujemy sposób obsługi tego wyjątku dodając odpowiednie exception handlers. W naszym przypadku dodajemy Wrap Handler. W jego właściwościach określamy komunikat błędu oraz typ wyjątku, który ma być zgłoszony (rys. 4). Taka definicja pozwoli nam uzyskać identyczny efekt jak ten, który mieliśmy dotychczas.


Rys. 4 Właściwości wrap handlera.

Ostatnim krokiem przekształcenia jest zamiana kodu obsługi błędu. Korzystając ze statycznej metody HandleException klasy ExceptionPolicy, wywołujemy obsługę wyjątku. Zauważmy, że jako drugi parametr należy podać nazwę wytycznej, na podstawie której wyjątek będzie obsługiwany.

public class UserIdentityDAO
{
    public static UserIdentity GetUserIdentity(string identifier)
    {
        UserIdentity identity = null;
        try
        {
            SqlConnection conn = new SqlConnection(Properties.Settings.Default.UserDatabaseConnectionString);
            conn.Open();
            //Kontynuacja...
        }
        catch (Exception ex)
        {
            //Robimy co potrzebujemy, na przykład
            //zapisujemy info o błędzie
            bool rethrow = ExceptionPolicy.HandleException(ex, "Data Access Policy");
            if (rethrow)
                throw;
        }
        return identity;
    }
}


Kompilujemy kod i uruchamiamy. Zachowanie aplikacji powinno być identyczne jak przed refaktoryzacją.

W następnej kolejności będziemy refkatoryzować warstwę logiki biznesowej. W tej warstwie, podobnie jak w warstwie dostępu do bazy danych zdefiniujemy własne typy wyjątków. Postępowanie z pojawiającymi się wyjątkami będzie jednak inne. Tak jak pisałem wcześniej chcemy ukrywać informacje, które są zbędne z punktu widzenia użytkownika. Informacje techniczne o wewnętrznej budowie i stanie systemu powinny zostać ukryte, jednak wciąż powinny być dostępne dla osoby odpowiedzialnej za utrzymanie aplikacji. EHAB dostarcza nam mechanizmu, który pozwoli wykonać opisany scenariusz. Wyjątek przechwytywany w warstwie logiki biznesowej będzie logowany (np. do logu systemowego) i otrzyma unikatowy identyfikator (taki identyfikator EHAB nadaje każdemu obsługiwanemu wyjątkowi). Następnie do warstwy interfejsu użytkownika zostanie przekazany nowy wyjątek nie zawierający szczegółowych informacji o wyjątku, a tylko prostą informację dla użytkownika zawierającą unikalny numer błędu. Na podstawie tego numeru osoba utrzymująca aplikację będzie mogła powiązać wyjątek od użytkownika z wpisem w logu.

Najpierw zdefiniujmy nowy typ wyjątku w warstwie logiki biznesowej, który będzie odpowiadał zgłaszanemu z warstwy dostępu do bazy danych wyjątkowi DatabaseException. Nazwijmy go OperationExecutionException. Teraz przejdźmy do konfiguracji EHAB. W wytycznej (policy) dla logiki biznesowej dodajmy typ obsługiwanego wyjątku, czyli DatabaseException i ustawiamy dla niego PostHandlingAction na ThrowNewException. Następnie dodamy dwa handlery. Zadaniem pierwszego będzie logowanie informacji o wyjątku, a drugiego – zastąpienie wyjątku nowym wyjątkiem. Do osiągnięcia pierwszego celu wykorzystamy Logging Handler, a drugiego – Replace Handler.

Dodając Logging Handler doda się również sekcja konfiguracyjna LAB. Na nasze potrzeby możemy zostawić tam wartości domyślne. W konfiguracji tego handlera musimy określić dwie rzeczy. Po pierwsze LogCategory – wybieramy wartość domyślną czyli General. Dostępne kategorie można zmieniać w konfiguracji LAB. Drugą własnością jest FormatterType. Należy tu wskazać klasę, która zajmie się zapisywaniem informacji o wyjątku. Wraz z EHAB mamy dostępne dwa: TextExceptionFormatter oraz XmlExceptionFormatter. Wybieramy pierwszy. Domyślnie LAB jest skonfigurowany, aby informacje zapisywać do logu systemowego  o nazwie Application.

Konfigurując drugi handler podajemy komunikat o błędzie oraz typ wyjątku, którym będzie zastąpiony przetwarzany wyjątek. W komunikacie o błędzie możemy użyć stałej {handlingInstanceID}, która zostanie przez EHAB zastąpiona unikalnym identyfikatorem przetwarzanego wyjątku.

Teraz pozostało nam już tylko zmodyfikować odpowiednio kod programu. Zmieniamy metodę logiki biznesowej tak, aby do obsługi wyjątku wykorzystywała EHAB:

public static bool AuthenticateUser(UserIdentity identity)
{
    try
    {
        UserIdentity dbIdentity = UserIdentityDAO.GetUserIdentity(identity.Identifier);
        return dbIdentity != null && identity.Password.Equals(dbIdentity.Password);
    }
    catch (Exception ex)
    {
        //Robimy co potrzebujemy, na przykład
        //zapisujemy info o błędzie
        bool rethrow = ExceptionPolicy.HandleException(ex, "Business Logic Policy");
        if (rethrow)
            throw;
    }
    return false;
}


Modyfikujemy też klasę okna zmieniając typ obsługiwanego wyjątku na OperationExecutionException. Po tych wszystkich zmianach aplikacja powinna się kompilować i działać poprawnie.

Podsumowanie

Dzięki refaktoryzacji do EHAB osiągnęliśmy kilka bardzo ważnych celów:
  1. Odseparowaliśmy wyjątki różnych warstw aplikacji.
  2. Wprowadziliśmy spójny mechanizm obsługi wyjątków.
  3. Ukryliśmy informacje techniczne o aplikacji przed zwykłym użytkownikiem jednocześnie zachowując je na potrzeby administratora aplikacji.


Zadanie konkursowe #2 - Exception Handling Application Block, Enterprise Library 3.1

Zgodnie z obietnicą i założeniami konkursu na koniec zadania do wykonania:
  1. Zrefaktoryzować kod aplikacji konkursowej tak, aby obsługę wyjątków w warstwie serwisowej (DataServices) można było konfigurować za pomocą EHAB (wytyczna - Data Services Policy).
  2. W aplikacji nie ma wyjątków rzucanych jawnie dlatego zakładamy, że z warstwy dostępu do danych (ObjectStorage) zgłaszane są wyjątki typu SqlException. Należy obsłużyć te wyjątki (dodać odpowiednie bloki try/catch) w następujących metodach serwisów:
    1. CustomerService: Set, FindAll
    2. FlightService: FindAll
    3. CustomerFlightService: Set, GetListByCustomerId
  3. Aplikacja powinna zapisywać szczegółowe informacje o pojawiających się tam wyjątkach, a do warstwy UI przesyłać wyjątek InvalidOperationException nie zawierający żadnych zbędnych informacji. Wyjątek musi zawierać jedynie odpowiedni komunikat dla użytkownika oraz identyfikator błędu przeznaczony dla administratora.
  4. W warstwie UI informacja o błędzie ma być prezentowana użytkownikowi za pomocą MessageBoxa.
Uwagi:
  1. Zalecane ujście dla zdarzeń to pliki tekstowe
  2. Jeśli w aplikacji konkursowej są jakieś zbędne logowania informacji o wyjątkach to należy je usunąć.
Rozwiązane zadania należy przesyłać na adres mgrzeg at gmail kropka com. Reguły wiadomości:
  1. Temat wiadomości: [ENTLIB EHAB] Rozwiązanie zadania konkursowego;
  2. Treść (może być w załączonym pliku .txt, .doc, .rtf) powinna zawierać krótki opis co zostało wykonane;
  3. Plik .zip zawierający kod  aplikacji konkursowej po refaktoryzacji. Można dołączyć również kod wykonywalny (bez bibliotek EntLib, etc. - tylko to, co niezbędne);
  4. Dane adresowe do wysyłki nagrody - NIE! Skontaktujemy się ze "szczęśliwcem" via email i ustalimy szczegóły.
Adresy:

    * Kod aplikacji przykładowej opisywanej w powyższym tekście;
    * Kod aplikacji konkursowej, którą należy poddać refaktoryzacji.

W razie wątpliwości etc. proszę zadawać pytania.

Powodzenia!
Opublikowane 1 października 2007 12:32 przez nuwanda

Komentarze:

# Konkurs Enterprise Library - Validation Application Block (VAB)

8 października 2007 15:01 by mgrzeg.net - Admin on Rails :)

Trzeci tekst cyklu konkursowego poświęconego Enterprise Library 3.x. Tym razem skupiamy się na Validation Application Block.

# Konkurs Enterprise Library – Policy Injection (PIAB)

15 października 2007 16:44 by Ww.CodeSculptor

Oto ostatnia odsłona konkursu. Tym razem wprowadzenie do wykorzystania PIAB przy scalaniu poszczególnych

Komentarze anonimowe wyłączone