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

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
NServiceBus - przykład 1: request/response
Dlaczego w ogóle omawiam ten przykład? Przecież wszystkie moje dotychczasowe notki dotyczące NServiceBus przekonywały Was, że ten schemat komunikacji jest zły. Otóż czasem jest on nieunikniony. Najlepszym przykładem zastosowania request/reposnse są wszelkiego rodzaju funkcje autoryzujące. Logiki związanej z autoryzacją (niezależnie od tego, co autoryzujemy — czy to użytkownika, czy transakcję, czy cokolwiek innego) nie chcielibyśmy rozpraszać w wielu elementach systemu. Dobrą praktyką związaną z zapewnieniem wysokiego poziomu bezpieczeństwa jest centralizacja logiki autoryzacji, co wiąże się niestety z koniecznością każdorazowego „odpytywania” usługi autoryzującej.

Schemat request/response omówię na przykładzie sample-a FullDuplex (aka RequestResponse) dostarczanego wraz z NServiceBus. Przed uruchomieniem przykładu polecam do app.config w projekcie „MyClient” skopiować sekcje Common.Logging oraz log4net z projektu „LoggingFromAppConfig” z sample-a GenericHost. Możecie także skorzystać ze zmodyfikowanej wersji pliku app.config dołączonej do tego posta. Dzięki temu komunikaty wypisywane na ekran będą czytelne.

Komunikacja request/response w NSB jest zwykle asynchroniczna. Co to oznacza? Ano to, że preferowanym sposobem postępowania jest przetwarzanie odpowiedzi w odpowiednim momencie w tle, bez blokowania wysyłającego żądanie (który może zająć się użyteczną pracą). Istnieją dwa sposoby rejestracji metody, która przetworzy odpowiedź i oba są zademonstrowane w sampe-u:

Sposób preferowany to implementacja interfejsu IHandleMessages:

    1 class DataResponseMessageHandler : IHandleMessages<DataResponseMessage>

    2 {

    3   public void Handle(DataResponseMessage message)

    4   {

    5       Console.WriteLine("Response received with description: {0}\nSecret answer: {1}",

    6           message.String, message.SecretAnswer.Value);

    7   }

    8 }


Jak już pisałem wcześniej, zawsze wszystkie zarejestrowane w danej instancji NSB handler-y mają szansę przetworzyć pasujące do nich komunikaty. Klasy implementujące interfejs IHandleMessages są automatycznie wykrywane przy starcie NServiceBus (który skanuje katalog z binariami naszej aplikacji). Tworzona jest wtedy mapa odwzorowująca znane typy komunikatów na zbiory handler-ów mogących je obsłużyć. W aplikacji przykładowej handler wypisuje na ekran fragmenty informacji zawarte w komunikacie.

Inny sposób to wykorzystanie interfejsu ICallback zwracanego przez wywołanie Send:

    1 Bus.Send<RequestDataMessage>(m =>

    2                                {

    3                                    m.DataId = g;

    4                                    m.String = "<node>it's my \"node\" & i like it<node>";

    5                                    m.SecretQuestion = "What's your favorite color?";

    6                                })

    7   .Register(i => Console.Out.WriteLine(

    8                      "Response with header 'Test' = {0}, 1 = {1}, 2 = {2}.",

    9                      Bus.CurrentMessageContext.Headers["Test"],

   10                      Bus.CurrentMessageContext.Headers["1"],

   11                      Bus.CurrentMessageContext.Headers["2"]));


Pozwala on zarejestrować callback (metoda Register), który zostanie wykorzystany do przetworzenia konkretnie jednego komunikatu, który zostanie wysłany w odpowiedzi na ten, którego dotyczy dana instancja ICallback. Kojarzenie komunikatu i callback-u odbywa się za pomocą identyfikatora Correlation Id przenoszonego przez wiadomości. Dlaczego sposób ten nie jest preferowany? Jest on problematyczny w wypadku komunikacji transakcyjnej. Kiedy klient "padnie" zaraz po wysłaniu żądania (i rejestracji callback-u) zarejestrowana procedura obsługi zostanie utracona i komunikat odpowiedzi nie zostanie poprawnie przetworzony po ponownym uruchomieniu klienta. Dobra rada jest więc taka: wykorzystujcie funkcjonalność ICallback tylko dla komunikacji nietransakcyjnej lub do realizacji dodatkowej (nie krytycznej) funkcjonalności (jak logowanie).

W API jest także dostępna jedna metoda, która pozwala na realizację synchronicznej komunikacji (jeśli komuś bardzo zależy). Jedna z przeładowanych wersji ICallback.Register zwraca jako wynik IAsynchResult, z którego możemy pobrać obiekt WaitHandle, który z kolei oferuję metodę WaitOne pozwalającą efektywnie zablokować wątek wywołujący Send do czasu otrzymania komunikatu odpowiedzi. Zmodyfikowany kod wygląda tak:

    1 .Register(i => Console.Out.WriteLine(

    2                   "Response with header 'Test' = {0}, 1 = {1}, 2 = {2}.",

    3                   Bus.CurrentMessageContext.Headers["Test"],

    4                   Bus.CurrentMessageContext.Headers["1"],

    5                   Bus.CurrentMessageContext.Headers["2"]), null).AsyncWaitHandle.WaitOne();


Jest to jednak feature dedykowany do specjalnych zastosowań i w większości przypadków nie powinien być wykorzystywany (jest po prostu SZKODLIWY).

Przykład FullDuplex ma jeszcze dwie ciekawe cechy. Pierwszą z nich jest demonstracja mechanizmu szyfrowania za pomocą klucza prywatnego. Obie instancje NSB (klient i serwer) mają w konfiguracji określony współdzielony klucz szyfrujący algorytmu AES (Rijndael). Konfiguracja ta jest banalnie prosta:

<RijndaelEncryptionServiceConfig Key="gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e7"/>


Drugą ciekawostką jest sposób uruchamiania (hostowana). Jak zapewne zauważyliście, w solution nie ma żadnego projektu wykonywalnego — tylko biblioteki klas. Otóż NServiceBus począwszy od wersji 2.0 zawiera aplikację generycznego hosta (dla zainteresowanych: NSB wykorzystuje bibliotekę TopShelf), która może być wykorzystana do hostowana usług NSB. Ta generyczna aplikacja pozwala na uruchamiania zarówno w trybie konsolowym (w Visual Studio, przez F5), jak i trybie usługi Windows (opcja „install”). Jest to wielkie ułatwienie w procesie debugowania i deployment-u.

Na koniec pozostaje mi jedynie zachęcić do ściągnięcia NServiceBus i zobaczenia sample-a na własne oczy.

Opublikowane 13 grudnia 2009 19:45 przez simon

Filed under:

Komentarze:

# Simon says... : NServiceBus - przykład 1: request/response @ 14 grudnia 2009 08:49

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

dotnetomaniak.pl

# re: NServiceBus - przykład 1: request/response @ 16 grudnia 2009 16:29

Witam,

Czy spotkałeś się z problemem sesji nhibernate w momencie obsługi żądania (handle) w nservicebus?

k1

# re: NServiceBus - przykład 1: request/response @ 17 grudnia 2009 07:17

Z problemem nie, ale zależy co masz na myśli. Jeśli to, że trzeba skonfigurować w NHibernate providera do sesji kontekstowej, to tak -- trzeba to zrobić, a nie jest to najlepiej opisany fragment NH:)

simon

# re: NServiceBus - przykład 1: request/response @ 17 grudnia 2009 11:33

A gdzie mozna znalezc jakikolwiek opis tego?

k1

# re: NServiceBus - przykład 1: request/response @ 17 grudnia 2009 11:40

http://nhforge.org/wikis/reference2-0en/context-sessions.aspx

Dla NSB powinno być skonfigurowane "ThreadStatic". O ile dobrze pamiętam, w wersji 2.0 NSB to się jakoś samo "automagicznie" konfiguruje, ale głowy nie dam.

simon

Komentarze anonimowe wyłączone