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

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
Przeprowadzka
Minął rok i dwa tygodnie odkąd zacząłem blogować na Zine. Przed Zine także, bodaj dwukrotnie, próbowałem blogować, ale próby te były nieskuteczne -- czegoś brakowało. Dopiero wzięcie udziału w czymś takim jak Zine pozwoliło mi "rozwinąć skrzydła" jako blogerowi. Przez ten rok było mi tu bardzo dobrze. Owszem, zdarzało mi się czasem milczeć tygodniami, ale średnia (produkowana ilość notek) nie wyszła tak zła.

Chciałbym podziękować wszystkim Twórcom Zine, a szczególnie Michałowi, za bardzo ciepłe przyjęcie mnie, jako świeżego blogera. Pora jednak dla mnie opuścić to "rodzinne gniazdo" i przejść na swoje. Jeśli chcecie dalej czytać moje przemyślenia, zaprszam na nowy adres: http://simon-says-architecture.com.

Przy okazji postanowiłem bardziej systematycznie pisać. Więcej będzie też notek po angielsku. Chciałbym, aby blog był dwujęzyczny. Jeśli, drogi Czytelniku, używasz feed-a mojego bloga (http://feeds.feedburner.com/szymonpobiega), aby czytać moje posty, nie musisz nic robic. Feed został automatycznie zaktualizowany. Wszystkich innych zapraszam albo bezpośrednio na bloga, albo do subskrypcji wspomnianego feeds.

Będę się starał, aby strona, oprócz oczywiście bloga, zawierała także inne treści. W szczególności chciałbym uczynić ją miejscem, gdzie możecie dowiedzieć się czegoś wiecej na temat moich projektów OSS.

Trzymajcie za mnie kciuki!

Szymon

opublikowano 24 stycznia 2010 15:18 przez simon | 0 komentarzy

NHibernate, NServiceBus i transakcje
Dziś chciałbym podzielić się z Wami moimi refleksjami na temat sposobu zarządzania transakcjami w NHibernate, ze szczególnym uwzględnieniem nietrywialnego przypadku, kiedy w ramach jednej transakcji wykorzystujemy zarówno NHibernate, jak i NServiceBus. Posłużę się w tym celu kodem DDDSample.Net.

Aby wprowadzić Was w klimat, przypomnę jak wygląda architektura DDDSample. Począwszy od najwyższego poziomu, występują tam następujące warstwy:
  • WebUI (ASP.NET MVC) - prezentacja danych, interfejs
  • Application - stanowi fasadę dla modelu domeny udostępniając interfejs poszczególnych akcji/komend
  • Domain - logika biznesowa żyje tutaj
  • Domain.Persistence.NHibernate - mapowania NHibernate oraz implementacje repozytoriów
Która warstwa odpowiada zatem za transakcje? Oczywiście Application. Dlaczego? Ponieważ jednostką izolacji są akcje/komendy — każda taka jednostka wykonywana jest w ramach osobnej transakcji.

Ponieważ transakcyjność postrzegam (zwykle) jako jedno z wymagań niefunkcjonalnych, implementuje ją za pomocą aspektów. Korzystam przy tym z możliwości mojego ulubionego kontenera Unity, jednak to samo można zrobić za pomocą niemal dowolnego innego kontenera. Moja warstwa Application składa się par (interfejs, klasa), przy czym interfejs zdefiniowany jest nie po to, aby umożliwić zmianę implementacji, ale tylko i wyłącznie po to, aby umożliwić AOP (tak, wiem, że można to samo osiągnąć za pomocą metod wirtualnych)

    1 /// <summary>

    2 /// Cargo booking service.

    3 /// </summary>

    4 public interface IBookingService

    5 {

    6    /// <summary>

    7    /// Registers a new cargo in the tracking system, not yet routed.

    8    /// </summary>

    9    /// <param name="origin">Cargo origin UN locode.</param>

   10    /// <param name="destination">Cargo destination UN locode.</param>

   11    /// <param name="arrivalDeadline">Arrival deadline.</param>

   12    /// <returns>Cargo tracing id for referencing this cargo.</returns>

   13    TrackingId BookNewCargo(UnLocode origin, UnLocode destination, DateTime arrivalDeadline);

   14    //...//


   15 /// <summary>

   16 /// Implementation of <see cref="IBookingService"/>.

   17 /// </summary>

   18 public class BookingService : IBookingService

   19 {     

   20    public TrackingId BookNewCargo(UnLocode originUnLocode, UnLocode destinationUnLocode, DateTime arrivalDeadline)

   21    {

   22       Location origin = _locationRepository.Find(originUnLocode);

   23       Location destination = _locationRepository.Find(destinationUnLocode);

   24       //...


Korzystam z tego w kodzie konfigurującym kontener DI:

    1 container.Configure<Interception>()

    2    //Ustaw sposób przechwytywania

    3    .SetInterceptorFor<IBookingService>(new InterfaceInterceptor())

    4    .SetInterceptorFor<IHandlingEventService>(new InterfaceInterceptor())

    5    //Dodaj nową "politykę"

    6    .AddPolicy("Transactions")

    7    //Wykorzystującą aspekt obsługi transakcji

    8    .AddCallHandler<TransactionCallHandler>()

    9    //I podłącz do wszystkich interfejsów z assembly

   10    .AddMatchingRule(new AssemblyMatchingRule("DDDSample.Application"));


Dzięki temu każde wywołanie fasad z warstwy Application automatycznie opakowywane jest w transakcje. Pozostaje jeszcze wyjaśnić, jak wygląda wspomniany aspekt. Oto kluczowy kawałek kodu:

    1 public class TransactionCallHandler : ICallHandler

    2 {

    3    private readonly ISessionFactory _sessionFactory;

    4    public TransactionCallHandler(ISessionFactory sessionFactory)

    5    {

    6       _sessionFactory = sessionFactory;

    7    }

    8    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

    9    {

   10       using (ITransaction tx = _sessionFactory.GetCurrentSession().BeginTransaction())

   11       {

   12          IMethodReturn result = getNext()(input, getNext);

   13          if (result.Exception == null)

   14          {

   15             tx.Commit();

   16          }

   17          return result;

   18       }

   19    }

   20    public int Order { get; set;}     

   21 }


Który wykorzystując API sesji kontekstowych tworzy nową transakcję, wywołuje właściwą metodę, po czym, jeśli nie wystąpił żaden wyjątek, zatwierdza transakcję. W przypadku wyjątku transakcja pozostaje niezatwierdzona, a wyjątek (nienaruszony) przelatuje do warstw wyższych.

Bardzo lubię ten kawałek kodu. Niestety nie sprawdza się on w moim ulubionym scenariuszu: NHibernate + NServiceBus. W takim przypadku potrzebuje transakcji rozproszonej System.Transactions. Swego czasu Ayende pisał o tym, że NHibernate "automagicznie" współdziała z System.Transactions. Ostatnio jednak pojawił się w NHibernate koncept ITransactionFactory, którego zadaniem jest (chyba?) poprawa jakości tego współdziałania. Domyślna implementacja fabryki, AdoNetWithDistrubtedTransactionFactory, jak sama nazwa sugeruje wspiera transakcje rozproszone. Niestety nie udało mi się sprawić, aby działała w najprostszym scenariuszu integracji z NServiceBus. Zrezygnowałem więc z tego ficzera i powróciłem do "starego dobrego" AdoNetTransactionFactory (który teraz trzeba sobie skonfigurować samemu). Niezbędna była jednak modyfikacja  mojego TransactionCallHandler:

    8    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

    9    {

   10       IMethodReturn result;

   11       SqlConnection sqlConnection = _sessionFactory.GetCurrentSession().Connection as SqlConnection;

   12       using (TransactionScope tx = new TransactionScope())

   13       {           

   14          if (sqlConnection == null)

   15          {

   16             throw new NotSupportedException("Only SqlConnection is supported.");

   17          }

   18          sqlConnection.EnlistTransaction(Transaction.Current);

   19          result = getNext()(input, getNext);

   20          if (result.Exception == null)

   21          {

   22             _sessionFactory.GetCurrentSession().Flush();

   23             tx.Complete();

   24          }                       

   25       }

   26       sqlConnection.EnlistTransaction(null);

   27       return result;        

   28    }


Zamiast korzystać z API transakcji NHibernate, korzystam bezpośrednio z System.Transactions.  Negatywnym skutkiem tego podejścia jest ograniczenie wspieranych baz do SQLServera 2005/2008. Niestety ADO.NET nie umożliwia niezależnego od sterownika bazy danych wpinania połączeń w transakcje rozproszone.

Pierwsze wywołanie EnlistTransaction wpina aktualne połączenie, na którym działa sesja NHibernate do transakcji rozproszonej. Drugie wywołanie (to z null-em) odłącza połączenie od transakcji. Jeśli transakcja ta nie została wcześniej zatwierdzona (tx.Complete()), zmiany są wycofywane na poziomie bazy danych.

Jest jeszcze jeden szkopuł. NHibernate domyślnie zwalnia połączenia związane z sesją najwcześniej, jak może. Jest to zachowanie optymalne z punktu widzenia wydajności, jednak w przypadku takiego zarządzania transakcjami, niepoprawne. Nie chcemy przecież, aby nasze połączenie, które podłączyliśmy do rozproszonej transakcji, zostało zamknięte. Aby temu zapobiec, musimy do konfiguracji NHibernate dodać następujący wpis:

<property name="connection.release_mode">on_close</property>


Na koniec jeszcze jedna niemiła informacja: powyższy sposób zarządzania transakcjami jest niekompatybilny z cache 2-go poziomu NHibernate. Co to oznacza? Otóż modyfikacje dokonane na danych w ramach tak zrealizowanych transakcji nie zostaną uwzględnione w cache 2-go poziomu. Nie sprawia to jednak problemu, jeśli nie modyfikujemy danych cache-owanych. Można więc próbować obejścia, polegającego na stosowaniu obu pokazanych wersji TransactionCallHandler w zależności od tego, czy transakcje rozproszone są wymagane.

Taka strategia powinna sprawdzać się dobrze, ponieważ wymaganie transakcji rozproszonych jest związane z odbieraniem / wysyłaniem komunikatów NServiceBus, a tego rodzaju akcje nie powinny modyfikować danych słownikowych (które są zwykle cache-owane).

opublikowano 13 stycznia 2010 18:19 przez simon | 1 komentarzy

Filed under: ,

ThoughtWorks Radar
Pewnie część z Was zdaje sobie z tego sprawę, ale dla mnie było to ciekawe odkrycie. Firma ThoughtWorks publikuje corocznie raport dotyczący technologii, praktyk i metodyk związanych z IT. Raport ten ma formę "radaru" — koła podzielonego na 4 pierścienie. Idąc od zewnątrz są to: hold (obserwować z daleka), asses (przyjrzeć się bliżej), trial (przetestować) oraz adopt (wdrożyć).

Czytając "radar" można dowiedzieć się wielu ciekawych rzeczy lub podłechtać swoją dumę, jeśli przewidywania "thoughtworker-ów" pokrywają się z własnymi. Oto według mnie najważniejsze wnioski z dokumentu:
  • Isnieje szansa, że język Java zbliża się do kresu swojego życia. Rzeczywiście, w porównaniu do C#, Java od dłuższego czasu stoi w miejscu, co w naszej branży oznacza bardzo szybkie cofanie się. Z drugiej strony, dla JVM powstaja jak grzyby po deszczu nowe, innowacyjne języki. Tu przewidywania "radaru" pokrywają się z moimi: koniec Javy (przynajmniej takiej, jaką ją znamy) jest bliski. Co zrobi Microsoft, aby przyciągnąć świetnie wyszkolonych developerów Java? Czy obserwowane od jakiegoś czasu otwarcie Microsoft na open source ma na celu przekonanie zatwardziałych dźawowców?
  • JavaScript staje się językiem "pierwszej kategorii". To chyba największy awans w tegorocznym radarze: poprzednio poza kołem, a teraz w samym jego centrum. JavaScript staje się wszechobecny. Osobiście, zupełnie tego nie rozumiem. Według mnie jest to wyjątkowo nieudany język i fakt, że zyskał tak ogromną popularność jest skutkiem tego, że korporacje nie były się w stanie dogadać, czym go zastąpić. Nie widzę miejsca dla JS, jeśli dziś wiele przeglądarek ma zanistalowanego Flasha, Javę i Silverlighta. Czemu strony HTML nie miałyby używać tych (lub innych) języków własnościowych do tworzenia efektów dynamicznych?
  • (moje ulubione) Wzrost znaczenia zwinnego podejścia do architektury oprogramowania. Istnieje cała grupa tematów, począwszy od doskonalenia metodyk zwinnych (Lean), a skończywszy na narzędziach do płynnych migracji schem bazodanowych, które sprawiają, że rola architekta się zmienia i ewoluuje w kierunku, który bardzo mi odpowiada: mentoringu, ewolucyjnego podejścia do architektury oraz zwiększania znaczenia metadecyzji (decyzji o tym, kiedy należy podjąć decyzję).
  • Inne trendy, które mnie osobiście cieszą, to chłodne podejście do RIA i entuzjastyczne do nierelaacyjnych baz danych.

opublikowano 10 stycznia 2010 21:24 przez simon | 8 komentarzy

NServiceBus - przykład 2: publish/subscribe
Wracam po świąteczno-noworocznej przerwie z drugą częścią tutoriala NServiceBus.

Drugi z przykładów, które chciałbym z Wami omówić to demo subskrypcji. W katalogu z przykładami NServiceBus znajdziecie go pod nazwą „PubSub”. Solution składa się z 4 projektów.
MyMessages zawiera definicje wymienianych komunikatów. Zwróćcie uwagę, że są tam dwa elementy: interfejs IEvent oraz klasa EventMessage z niego dziedzicząca. Skąd to i po co? I dlaczego to wytłuszczenie? Otóż NServiceBus posiada unikalną własność — umożliwia definiowanie komunikatów jako interfejsów oraz obsługuje ich dziedziczenie  (zarówno między interfejsami, jak i między interfejsami i klasami). Jak to działa? Jeśli zdecydowałem się odbierać (czy to przez subskrypcje, czy też „klasycznie” – request/response) pewien typ komunikatu, to mój endpoint będzie odbierał także wszystkie komunikaty, których typy dziedziczą z podanego.

Ale skończymy to teoretyzowanie i popatrzmy na przykład. MySubscriber1 zawiera handler dla komunikatu typu EventMessage. NServiceBus automatycznie przy starcie wykrywa ten fakt, odnajduje w pliku konfiguracyjnym adres kolejki wejściowej instancji NSB publikującej ten komunikat i zgłasza się do niej jako subskrybent.

MySubscriber2 wykorzystuje możliwość własnej inicjalizacji instancji NSB i wymusza pominięcie automatycznej subskrypcji.

    1 public class EndpointConfig : IConfigureThisEndpoint, IWantCustomInitialization
    2  {
    3      public void Init()
    4      {
    5          NServiceBus.Configure.With()
    6              .CastleWindsorBuilder() // just to show we can mix and match containers
    7              .XmlSerializer()
    8              .MsmqTransport()
    9                  .IsTransactional(true)
   10              .UnicastBus()
   11                  .DoNotAutoSubscribe() //managed by the class Subscriber2Endpoint
   12                  .LoadMessageHandlers();
   13      }
   14  }

Jest ona zrealizowana „manualnie” w klasie Subscriber2Endpoint. MySubscriber2 zawiera handler dla komunikatów typu IEvent.

„Skomplikowana” logika zawarta w Publisherze

var eventMessage = publishIEvent ? Bus.CreateInstance<IEvent>() : new EventMessage();

sprawia, że co drugi raz wysyłany jest komunikat typu EventMessage, a co drugi — anonimowa (dynamicznie generowana) implementacja IEvent. Zastanówmy się wobec tego jak zachowają się subskrybenci. Subscriber2, któremu obojętne co przetwarza, pod warunkiem, że to „coś” stosuje się do kontraktu IEvent będzie reagował na każdy opublikowany komunikat. Subscriber1 jest selektywny — nie interesuje go „byle jaka” dynamiczna implementacja IEvent. On chce tylko EventMessage. Będzie więc reagował na co drugi komunikat. Prawda, że sprytne?

Co ważne, decyzja kto jest zainteresowany danym komunikatem następuje na poziomie publikującego, a nie subskrybującego. Publisher nie wysyła do MySubscriber1 anonimowych implementacji IEvent, gdyż wie, że ten sobie tego nie życzy.

Ostatnim elementem przykładu jest implementacja interfejsu IAuthorizeSubscribtions. Pozwala ona na określenie, na podstawie danych o nadawcy, czy pozwolić na realizację danego żądania subskrypcji. Mechanizm ten pozwala skutecznie ograniczyć zakres publikacji do wyłącznie zaufanych maszyn i/lub użytkowników.

opublikowano 7 stycznia 2010 17:13 przez simon | 8 komentarzy

Filed under:

NetMX, reaktywacja
Dawno nie pisałem o tym, nieco zaniedbanym ostatnio, moim projekcie. Ostatnimi czasy jednak NetMX nieco ożył i dzieje się wokół niego całkiem sporo. Oto relacja z pola bitwy.

Długo męczyłem się z implementacją protokołu JSR-262 nie czyniąc właściwie wielkich postępów przez prawie rok. JSR-262 jest standardem komunikacji z serwerem JMX opartym o kilka powszechnie przyjętych specyfikacji WS-*, m.in. WS-Management, WS-Transport, WS-Enumeration oraz WS-Eventing. Winę za brak postępów przypisuję bardzo dużemu skomplikowaniu właściwie wszystkich tych protokołów.

Ku mojemu ogromnemu zaskoczeniu niedawno projektem zainteresował się Erich Eichinger. Zainteresował się do tego stopnia, iż przesłał mi patch-a zawierającego poprawki, dzięki którym udało mu się doprowadzić do stanu, że aplikacja JConsole (standardowej narzędzie diagnostyczne Java oparte o JMX) łączy się z serwerem NetMX. Niestety zaraz potem połączenie było zrywane...

Tak mnie jednak to jego zainteresowanie zmobilizowało, że w środę rano wersja 52184 trunk NetMX była już w stanie komuniktować się z JConsole w zakresie pobierania wartości atrybutów bean-ów oraz wywoływania ich operacji. Okazało się jednak, że aby pracę mogły posuwać się dalej niezbędna jest zakrojona na szeroką skalę refaktoryzacja kodu związanego z WS-Transport i WS-Management.

I tak wersja 52632 trunk zawiera napisany całkowicie od podstaw kod obsługi tych dwóch protokołów. Co więcej, kod ten jest na tyle uniwersalny, że planuję udostępnić go jako zupełnie osobny projekt — WS-Man.NET.

W tym momencie każdą wolną chwilę spędzam rozwijając WS-Man.NET o obsługę Enumeration oraz Eventing. Erich zaś przygotował bardzo dobry tutorial, który świetnie pokazuje, jak prosty w użyciu jest NetMX. Jego rozwiązanie wykorzystuje możliwości integracji NetMX ze Spring.Net aby zdalnie sterować poziomem logowania w aplikacji.

Będę Was informował na bieżąco o dalszym postępie prac...

opublikowano 21 grudnia 2009 19:37 przez simon | 0 komentarzy

Filed under: ,

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.

opublikowano 13 grudnia 2009 19:45 przez simon | 5 komentarzy

Filed under:

Szlachtna Paczka
Hej:)

Zawieszam na chwilę moją tradycję nie pisania niczego niezwiązanego z IT. Trwa teraz taka akcja — nazywa się Szlachetna Paczka. Być może znacie ją pod nazwą "Świąteczna Paczka", pod jaką występowała w ubiegłych latach.

O co chodzi? Paczka stara się (poprzez swoich wolontariuszy) dotrzeć do osób najbardziej potrzebujących, następnie dowiedzieć się wszystkiego na temat ich potrzeb i tego, co naprawdę mogłoby odmienić ich życie. Potem takie osoby trafiają do internetowej bazy danych. I teraz coś dla Was:) Możecie sobie kliknąć na link i pomóc jednej z tych rodzin. Paczkę można robić w kilka osób, znajomych, kolegów z pracy. Wtedy koszt na osobę będzie naprawdę niewielki, a sprawicie komuś ogromną radość na Święta.

Dlaczego o tym piszę? Ponieważ jestem w tym roku wolontariuszem Paczki. Znam osobiście część z rodzin z naszego rejonu (Kraków, magazyn Na Wzgórzach 1A) i mogę zaświadczyć, że nie są to ludzi o postawie roszczeniowej, którzy nic nie robią i twierdzą, że "wszystko im się należy". Wybraliśmy rodziny, które nie ze swojej winy znalazły się w trudnej sytuacji, a dzięki Waszej pomocy mogłyby znów stanąć na nogi.

Bądź Szlachetny! Zrób paczkę!

opublikowano 7 grudnia 2009 06:07 przez simon | 2 komentarzy

DDDSample.Net 0.5
Śpieszę donieść, że właśnie pojawiła się nowa (0.5) wersję projektu DDDSample.Net. Ponieważ dawno o DDDSample nie pisałem, pozwolę sobie przypomnieć, co to takiego ten projekt i jaki jest jego aktualny stan. Projekt jest portem Java-owego DDDSample, demonstracją siły podejścia Domain-Driven Design w projektowaniu aplikacji biznesowych.

Obecnie funkcjonalności biznesowe obejmują:
  • Rejestrację nowego towaru do spedycji
  • Wyznaczenie (niekoniecznie) optymalnej trasy transportu
  • Rejestrację nowego "zdarzenia" transportowego (np. odebrano towar, załadowano towar)
  • Śledzenie postępu transportu
Interfejs użytkownika zbudowany jest w oparciu o ASP.NET MVC. Komendy użytkownika przekazywane są, za pośrednictwem warstwy aplikacji dokonującej translacji żądań, do Modelu Domeny. Model zawiera całość logiki aplikacji. Podzielony jest na 3 główne obszary (zwane agregatami):
  • Location - zawierający logikę związaną z lokalizacją - jest to właściwie generyczna poddomena.
  • Cargo - zawierający logikę związaną z rejestracją, wyznaczaniem trasy oraz śledzeniem postępu transportu
  • Handling - zawierający logikę związaną z rejestracją zdarzeń transportowych
Począwszy od wersji 0.5 komunikacja pomiędzy dwoma ostatnimi agregatami zrealizowana jest asynchronicznie, z wykorzystaniem NServiceBus. Co nam to daje? Podstawowe wykorzystanie agregatu Cargo to odczyt danych (na potrzeby śledzenia postępu). Podstawowe wykorzystanie agregatu Handling to zapis danych (na potrzeby rejestracji zdarzeń). Dzięki oddzieleniu tych dwóch możemy zastosować różne sposoby skalowania w tych dwóch obszarach systemu. A to tylko jeden z wielu przykładów...

Zapraszam do ściągnięcia DDDSample.Net i poeksperymentowania. Projekt dostępny jest w formie instalatora MSI (w sekcji downloads) lub w formie źródeł. MSI nie instaluje źródeł, a jedynie aplikację ASP.NET.

opublikowano 4 grudnia 2009 06:29 przez simon | 0 komentarzy

Re: NHibernateStarter...
Czyli nawiązanie do świetnego posta Procent-a na temat jego sposobu budowy aplikacji. Poniżej zamieszczam moje aktualne poglądy na ten temat. Zieloną czcionką wyróżniłem fragmenty porównujące oba rozwiązania.

O czym w ogóle mowa

Zawsze na początku należy sobie zadań takie pytanie. O jakich w ogóle aplikacjach mówimy? Procent opisywał przypadek nietrywialnej aplikacji biznesowej (OLTP), więc i ja posłużę się takową. Termin "nietrywialny" oznacza dla mnie znaczący stopień skomplikowania i/lub zmienności domeny problemu i implikuje (dla mnie) konieczność zastosowania praktyk związanych z Domain-Driven Design. Nie oznacza to, że do wszystkiego stosuję DDD, o nie! Akurat przypadkiem ostatnio jestem zaangażowany w tworzenie tego rodzaju systemów...

Poniższy stanowi dla mnie punkt wyjścia — swego rodzaju model referencyjny, który ułatwia mi projektowanie kolejnych aplikacji, ponieważ nie muszę zaczynać całego procesu za każdym razem od początku. Nie jest jednak sztywny i zawsze konfrontuje go z wymaganiami (funkcjonalnymi i niefunkcjonalnymi) konkretnej budowanej aplikacji.

Cebula ma warstwy, ogry mają warstwy...

...i aplikacje także mają warstwy. W moim referencyjnym modelu aktualnie znajdują się cztery warstwy fizyczne (tiers):
  • cienki klient (przeglądarka)
  • warstwa GUI (ASP.NET)
  • warstwa aplikacji
  • serwer bazy danych (RDBMS)
GUI ASP.NET jest oczywiście hostowane na IIS. Warstwa ta komunikuje się bezpośrednio w warstwą bazy danych. Warstwa aplikacji zrealizowana jest za pomocą serwisu Windows, który pozwala na realizację:
  • zadań harmonogramowanych
  • usług WCF
  • usług NServiceBus
Ta warstwa także ma bezpośredni dostęp do bazy danych. Nie ma w moim modelu miejsca na przesyłanie danych Web Service-em, typowego dla promowanego przez Microsoft modelu n-tier. Dlaczego? Dlatego, że, jak zaraz się dowiecie, nie wykorzystuję (prawie) warstwy dostępu do danych, więc nie ma wysyłających i odbiorców danych. Nie kastruję swojej architektury wykorzystując O/RM jedynie jako implementację serwisu dostępu do danych.

Warstwy logiczne (layers) są w większości wspólne zarówno dla GUI, jak i dla warstwy aplikacji:

Warstwa persystencji modelu domeny

Zawiera implemenację tzw. repozytoriów, czyli elementów modelu domeny, których zadaniem jest wytworzenie iluzji, że całość modelu znajduje się w pamięci. Interfejs repozytoriów ogranicza się do metod pobierających (zwykle po ID) oraz insertujących dla najważniejszych obiektów biznesowych (aggregate roots). Drugim elementem warstwy persystencji są mapowania O/RM odwzorowujące klasy modelu domeny na struktury bazodanowe.

(Szczątkowa) warstwa dostępu do danych

Zawiera metody dostępu do danych niezbędne do realizacji elementów interfejsu użytkownika, w szczególności takich, jak widoki list oraz szczegółów (master-detail). Dla każdego obiektu, który jest osobno prezentowany na GUI istnieje interfejs IXxxFinder (wraz z implementacją). Interfejs ten typowo zawiera metody: GetById, Find, GetCount, FindBySomething itp. Zwracają one obiekty DTO, które są tworzone nie na bazie obiektów modelu domeny (AutoMapper?), ale na bazie danych z bazy.

To różnica w porównaniu do tego, co proponuje Procent. Dlaczego wybrałem takie rozwiązanie? Dlatego, że pozwala mi oddzielić łatwo logikę domenową od GUI. Obiekty modelu nie muszą eksponować danych na potrzeby interfejsu, a co za tym idzie — są bardziej stabilne. Wszelkie komendy z GUI do modelu domeny przechodzą przez warstwę aplikacji, dzięki czemu GUI w żadnym punkcie nie styka się bezpośrednio z modelem.

I jeszcze drobny szczegół implementacyjny: zwykle pakuję tę warstwę wraz z warstwą persystencji modelu do jednego assembly, aby uprościć strukturę wdrożeniową.

Warstwa modelu domeny

Tu zlokalizowane jest serce aplikacji — większość logiki biznesowej. Ta warstwa nie referuje zwykle do żadnej zewnętrznej biblioteki. Kod ma być czytelny nawet dla nie-programisty — powinien mieć sens biznesowy. Na temat tego, co zawiera dobry model można poczytać tu i tam, nie będę więc się na ten temat rozpisywał. To, co jest ważne w kontekście tego posta to to, że z tej warstwy nie ma odwołań do bazy danych. Żadnych.

Warstwa aplikacji

Jest bardzo cienką warstwą zlokalizowaną ponad modelem domeny. Stanowi jego fasadę. Wszystkie odwołania do modelu przechodzą tędy. Faktycznie jest to zestaw pod-warstw zlokalizowanych na tym samym poziomie abstrakcji dostosowanych do "aktora" wywołującego dany przypadek użycia:
  • użytkownika, poprzez interfejs WWW ASP.NET
  • infrastruktury komunikacji asynchronicznej (NServiceBus)
  • infrastruktury komunikacji synchronicznej (WCF)
  • modelu domeny
Ten ostatni przypadek jest szczególnie interesujący. Dotyczy on zdarzeń domenowych (jak to opisał Udi): obiekty modelu zgłaszają zdarzenia, które są obsługiwane przez obiekty warstwy aplikacji. Jest to sposób na eleganckie wypychanie informacji z modelu.

W tej warstwie wykorzystuję NHibernate bezpośrednio lub za pośrednictwem repozytoriów (w zależności od nastroju, czytaj: specyfiki projektu). Nie mam, podobnie jak Procent, żadnych oporów przed bezpośrednim dostępem do O/RM-a. Wszelkie warstwy abstrakcji nie mają sensu, ponieważ i tak zmiana O/RM-a na inny (EF 4?) jest nierealna. Nie używam jednak konstrukcji typu DataAccessFacade.InTransaction, ponieważ moimi transakcjami zwykle zarządza zewnętrzny framework (NSB). Tylko w wypadku wywołań z poziomu ASP.NET wykorzystuję transakcje bezpośrednio, ale nawet w tym wypadku są to nie transakcje NHibernate, ale System.Transactions. Dlaczego? Ponieważ nigdy nie wiem, czy dany przypadek użycia nie zostanie rozbudowany o wysyłanie komunikatów NSB, a w tym wypadku muszę użyć transakcji rozproszonych (SQL + MSMQ). System.Transactions zapewnia mi automatyczną, przeźroczystą eskalację transakcji.

GUI ASP.NET / aplikacja serwisu Windows

Nic specjalnego w tej warstwie — ot standardowe aplikacje.

Podstawa to dobry stos

W tym wypadku chodzi o stos technologii. Chodzi o zestaw narzędzi, którymi posługujesz się tworząc aplikację. Z czasem każdy wypracowuje sobie swoje własne preferencje. Moje dosyć szybko ewoluują. Stan na dziś jest następujący:

NHibernate

Używam NHibernate także jako warstwy dostępu do danych, ale przede wszystkim jako biblioteki do persystencji modelu domeny. O ile tę pierwszą dałoby się łatwo wykonać samemu, o tyle ta druga jest bardziej skomplikowana i mocno wykorzystuje zaawansowane funkcje NHibernate, takie jak leniwe ładowanie, cache 2-go poziomu czy też dziedziczenie.

W przeciwieństwie do Procenta wykorzystuję kontekstowe sesji w NHibernate (zamiast delegata ją zwracającego). Dlaczego? Ponieważ w porównaniu z jego modelem (o ile go dobrze rozumiem) u mnie NHibernate wykorzystywany jest w znacznie mniejszej liczbie klas. Nie boli mnie więc specjalnie wstrzykiwanie obiektu ISessionFactory i pobieranie z niego sesji kontekstowej.

Nie korzystam także z Fluent NHibernate. Nie widzę, aby jego użycie dawało jakąś znaczącą przewagę nad plikami hbm, szczególnie, jeśli podepnie się schemy XSD do Visual Studio, a R# przy zmianie nazwy property sam aktualizuje hbm-a.

W chwili obecnej rozważam zastąpienie NHibernate przez rozwiązanie Marka Nijhofa oparte o ideę CQRS Grega Younga w następnym projekcie. Oczywiście, o ile projekt na tyle niewielki, abym mógł pozwolić sobie na taki eksperyment.

NServiceBus

O tej bibliotece wiele pisałem i wiele jeszcze planuje napisać. Obecnie jest ona głównym mechanizmem komunikacji wykorzystywanym w moich aplikacjach. Komendy zlecające wykonanie operacji na modelu transportowane są kolejkami MSMQ. Efekt zaś (wypychany w formie zdarzeń domenowych) jest propagowany za pomocą publikowanych wiadomości. Więcej na temat wykorzystania NSB w omawianym rodzaju aplikacji można przeczytać tutaj.

RhinoMocks

Jeśli już korzystam z frameworku do mock-owania, wybieram ten. Staram się jednak nie korzystać. Dlaczego? Ponieważ dobrze zaprojektowany i wykonany model domeny może być testowany w izolacji od bazy danych bez użycia mockera. Jeśli już koniecznie muszę coś zamockować, korzystam ze znakomitego tutoriala by Procent.

MSTest

To pewnie kontrowersyjna opinia. Używam MSTest, ponieważ odpalanie testów NUnit bez R# lub TestDriven.NET jest mało praktyczne. Zauważyłem, że adopcja testowania jest większa, jeśli testy można uruchamiać prościej. Gdybym miał ten komfort, że każdy programista dysponowałby R#, wybrałbym NUnit.

W chwili obecnej eksperymentuje z różnymi frameworkami wspierającymi BDD. Myślę, że w następnym projekcie spróbuje wykorzystać których eksperymentalnie.

Unity

Kolejny kontrowersyjny wybór. Unity używam z trzech powodów. Po pierwsze jest lekki (niewiele kodu). Po drugie przez sentyment (przeczytałem kod Unity, napisałem doń kilka add-in-ów, znam go jak własną kieszeń). Po trzecie zaś Unity ma proste API, które jet zrozumiałe dla programistów nie znających wszystkich słodyczy syntaktycznych C# (lambdy, closure-y).

Nie potrzebuję zwykle zaawansowanych funkcji kontenera, które są wbudowane w Spring-a, czy Castle, więc nie chce za nie płacić dodatkowym skomplikowaniem.

Common.Loggin i log4net

Przez długi czas byłem wielkim fanem Enterprise Library i jej frameworku do logowania. Niestety okazało się, iż jego użycie powoduje problemy ze strony NHibernate, NServiceBus oraz Quartz.Net. Biblioteki te albo korzystają bezpośrednio z log4net, albo używają Common.Logging jako abstrakcji. Ostatecznie poddałem się. Szczególnie, że z czasem okazało się, że nie potrzebuje także pozostałych modułów EntLib (poza jednym, ale o tym zaraz).

Quartz.Net

Quartz jest świetną biblioteką do harmonogramowania zadań. Ma ogromne możliwości, z krtrych zwykle wykorzystuję maleńki ułamek — uruchamiania zadań co zadaną ilość czasu. API Quartz jest jednak tak proste, że nie czuję się przezeń przytłoczony, a mam komfort, że zawsze mogę wpiąć się w zaawansowaną funkcjonalność.

Policy Injection Application Block

PIAB-a używam jako uniwersalnego rozwiązania AOP. Świetnie integruje się z Unity pozwalając na bardzo wygodną realizację takich aspektów jak transakcje czy logowanie wyjątków. Dodatkowo daje mi komfort w postaci możliwości określania polityk w app.config. Dzięki temu mogę spać spokojnie — kiedy moja aplikacja się wysypie administrator może samodzielnie zmienić politykę tak, aby logować każde wywolanie metody itp.

Topshelf

Jest to efektu uboczny projektu MassTransit. Topshelf to uniwersalna biblioteka do budowy serwisów Windows. Poza wieloma świetnym funkcjami, których nie zamierzam tu omawiać, ma jeden killer-feature: projekt wykorzystujący Topshelf uruchamia się jak normalna aplikacja konsolowa z poziomu Visual Studio, ale może być zainstalowany jako serwis Windows na środowisku produkcyjnym.

Testowanie

Osobiście jestem zwolennikiem testowania jednostkowego. Jak już wspominałem, dzięki modelowi domeny nie muszę się uciekać do framework-ów mockujących, aby przetestować model w izolacji (izolacja jest niezbywalną cechą samego modelu). Doceniam także sens posiadania kilku testów integracyjnych, które testują za jednym razem całóść funkcjonalności aplikacji, z tym że w tym wypadku moim celem nie jest 100% pokrycia logiki, ale testowanie wszystkich mechanizmów.

Dlatego też w wypadku testów integracyjnych, mimo iż stosuję SQLite (podobnie jak Procent), używam go w trybie plikowym z możliwością otwarcia wielu sesji. Jest to dla mnie warunek niezbędny, aby sprawdzić, czy dobrze zarządzam sesjami. Gdybym przez całą długość testu (który przechodzi przez wiele przypadków użycia) wykorzystywał jedną sesję, byłby to właściwie test bez O/RM-a. Dzięki wykorzystaniu wielu sesji testowaniu poddawana jest także poprawność mapowań NHibernate.

W ostatnio budowanym modelu pokusiłem się jednak o (w moim mniemaniu) śmiały eksperyment. Zamiast testować jednostkowo, stworzyłem większy (kilkanaście testów) zestaw testów integracyjnych, który pokrywa dokładnie 100% kodu modelu domeny. Pierwszy wniosek, jaki z eksperymentu wyciągnąłem to objawienie, że potrzeba o rząd wielkości mniej testów integracyjnych, aby zapewnić ten sam stopień pokrycia (w porównaniu do testów jednostkowych). Oczywiście, jak już pisałem, 100% niczego nie udowadnia. Ciekaw jestem, jak mój eksperyment sprawdzi się w praktyce patrząc z perspektywy roku czy dwóch.

A Ty, drogi Czytelniku? Jaki jest Twój sposób na budowanie aplikacji OLTP?

opublikowano 1 grudnia 2009 09:12 przez simon | 1 komentarzy

NServiceBus - możliwości 2
Oto dalszy ciąg opowieści o możliwościach biblioteki NServiceBus. W poprzedniej części mogliście przeczytać głównie o wysyłaniu komunikatów. Tym razem postaram się opowiedzieć więcej o odbieraniu komunikatów oraz o ...

Odbieranie komunikatów

NServiceBus nie oferuje bezpośrednio żadnego API służącego do odbierania komunikatów. Odbieranie (a zaraz po nim – przetwarzanie) odbywa się automatycznie na podstawie zarejestrowanych w danej instancji szyny obiektów obsługi komunikatów, tzw. message handler-ów. Aby umożliwić przetwarzanie danego typu wiadomości wystarczy zaimplementować generyczny interfejs IHandleMessages<T> i zapewnić, że assembly zawierające handler-a znajdzie się w ścieżce poszukiwania DLLek aplikacji hostującej szynę.

Zwróćcie uwagę na niecodzienną nazwę interfejsu. Jest to konwencja proponowana przez Udiego Dahana – aby nazwa interesu była zdaniem oznajmującym informującym o jego roli. Osobiście jestem raczej sceptycznie do tego pomysłu nastawiony, ale fakt jest taki, że w NServiceBus konwencja ta stosowana jest coraz szerzej.

Może równolegle istnieć wiele klas obsługujących pojedynczy typ komunikatu. Każda z nich może być np. zainteresowana jedynie podzbiorem przesyłanych danych. W takim wypadku, jeśli szyna pracuje w trybie transakcyjnym, transakcja obejmuje wykonanie wszystkich handler-ów danego komunikatu. W przypadku niepowodzenia jednego z nich, przetwarzanie jest przerywane, a transakcja – wycofywana.

Kolejka komunikatów zatrutych

To bardzo niewybitne tłumaczenie poison message queue. Idea jest prosta: ponieważ komunikaty w kolejce są przetwarzane w trybie FIFO, a komunikat, którego przetworzenie zakończy się błędem jest zwracany do kolejki, jedna "zatruta" wiadomość mogłaby skutecznie zablokować całe przetwarzanie – serwer próbowałby ją obsłużyć do końca świata (do 2012-go?).

Odpowiedzią na ten problem jest specjalna kolejka, do której trafiają komunikaty, których nie uda się przetworzyć określoną ilość razy. Sensowną wartością ilości ponowień jest 5. Pozwala to zminimalizować prawdopodobieństwo, że komunikat wyląduje w kolejce błędów na skutek nieprzewidywalnych zdarzeń losowych, typu chwilowa utrata połączenia z bazą danych.

NServiceBus wymaga, aby dla instancji szyny określić nazwę kolejki komunikatów zatrutych oraz liczbę ponowień przetwarzania. Parametry te znajdują się w dedykowanej sekcji  konfiguracyjnej.

To be continued...

opublikowano 27 listopada 2009 15:44 przez simon | 1 komentarzy

Filed under:

NServiceBus w kontekście zwykłej aplikacji - notka sponsorowana
Niniejsza notka została zamówiona przez Procenta za pomocą komentarza do poprzedniej notki. Chcesz zamówić notkę? Zostaw komentarz:)

Jak już napisałem, NServiceBus pozwala budować szyny informacyjne. Jest jednak (w przeciwieństwie do ciężkich rozwiązań ESB) zorientowany na wykorzystanie w pojedynczym systemie. Czym jest system, to już każdy może sobie odpowiedzieć. Może to być jedna mała aplikacja, może być także cały zestaw aplikacji CRM, ERP i innych stanowiących kompleksowy system informatyczny obsługujący np. internetowy sklep z warzywami.

Nie zrozumcie mnie źle, NServiceBus może być użyty do komunikacji z innym systemem nie wykorzystującym NSB, jednak jest to przypadek szczególny. Zdecydowanie lepiej biblioteka ta sprawdza się jako wewnętrzny mechanizm komunikacji dla luźno powiązanego systemu. W takim kontekście NSB może nam zaoferować możliwość osiągnięcia:
  • wysokiej przepustowości
  • wysokiej dostępności
  • lepszego modelowania procesów biznesowych
Jak to możliwe? Zaraz opowiem. Dla niecierpliwych tylko krótka informacja: wszystko to dzięki asynchronicznej komunikacji.

Wysoka przepustowość

Powiedzmy, że system składa się z trzech modułów A, B i C. Tylko moduł A udostępniany jest na zewnątrz, ale moduł ten musi wywołać usługę modułu B, aby wykonać swoją pracę. Moduł B wewnętrznie korzysta zaś z C — ot taki łańcuszek szczęścia. Możemy go zaimplementować dosyć za pomocą usług Web Service korzystając ze wsparcia WCF dla transakcji rozproszonych. Ma to jednak wiele negatywnych konsekwencji, o czym miałem okazję już mówić przy okazji spotkania KGD.NET. Najważniejszymi problemami, z punktu widzenia przepustowości, są:
  • blokowanie zasobów bazodanowych jednocześnie w 3 bazach danych przez transakcje rozproszoną
  • blokowanie wątków oczekujących na odpowiedź Web Service z usług B oraz C
Mając do dyspozycji narzędzia oferowane przez NServiceBus możemy tak zaprojektować system, że moduły A, B i C przesyłają sobie komunikaty zlecające zadania asynchronicznie. Jeśli moduł A do przetworzenia komunikatu żądania potrzebuje informacji X z modułu B, a moduł B produkuje informację X jako wynik swojego przetwarzania, to zwykły schemat request-response:



można zastąpić następującym:



Dzięki temu nikt na nikogo nie czeka. Zarówno zadania (strzałki czerwone), jak i stan (strzałki niebieskie) płyną od modułu do modułu. Jest to analogia taśmy produkcyjnej, podczas gdy rozwiązanie synchroniczne bardziej przypomina pracę rzemieślnika posiadającego dwóch pomocników.

Wysoka dostępność

Popatrzcie jeszcze raz na diagramy sekwencji dla przepustowości. Co się stanie, jeśli moduł C na jakiś czas zostanie wyłączony? Żadne żądanie klienta nie będzie mogło być obsłużone. Pozostałe dwa moduły są kompletnie bezradne i uzależnione od działania modułu C.

A teraz przypadek z NSB. Ponieważ informacje X i Y są przesyłane asynchronicznie do modułów ich potrzebujących i są tam przechowywane lokalnie, brak dostępności modułu C oznacza, że dane będą przez jakiś czas nieco nieaktualne. Taki stan rzeczy jest prawdopodobnie całkowicie akceptowalny. Kiedy tylko moduł C zostanie uruchomiony ponownie, przetworzy zaległe żądania i opublikuje nowe wartości danej Y. Klient niczego nie zauważy.

Lepsze modelowanie procesów biznesowych

Często zdarza się sytuacja, że w systemie wykonujemy na tych samych obiektach kolejno kilka zadań: A, B, C. Pomyślne zakończenie zadania A umożliwia wykonanie B i tak dalej. Często realizuje się to za pomocą kilku wątków, które okresowo odpytują bazę danych czy czasem nie istnieją jakieś obiekty w stanie do wykonania zadania X i jeśli istnieją, wykonują zadanie. Dla programistów takie podejście jest oczywiste. Dla ludzi biznesu nie koniecznie. W ich języku wymaganie to można zapisać jako: wykonanie zadania A powoduje rozpoczęcie zadania B. Stosując wspomniane rozwiązanie gubimy po drodze ten związek przyczynowo-skutkowy.

Po co nam tutaj NServiceBus? Otóż zamiast wyszukiwać obiekty w określonym "stanie" można, po pomyślnym wykonaniu zadania publikować na szynie NSB zdarzenie zadanie X wykonane pomyślnie. Co z tego, że jedynym subskrybentem zdarzenia będzie ten sam system, który je publikuje? W niczym to nie przeszkadza, a za to sprawia, że kod nareszcie przypomina biznes, który wspiera i automatyzuje.

opublikowano 21 listopada 2009 12:30 przez simon | 10 komentarzy

Filed under:

NServiceBus - Message Broker vs Message Bus
Message Broker i Message Bus to dwa konkurencyjne wzorce architektoniczne związane z integracją systemów. Celem obu jest rozwiązanie problemu komunikacji między wieloma systemami (usługami) w taki sposób, aby zapobiec tzw. Spaghetti-Oriented Architecture, czyli komunikacji każdy-z-każdym.

Message Broker (znany także jako hub-and-spoke) zakłada istnienie jednago centralnego punktu, przez który przechodzą wszystkie komunikaty w danym systemie. Broker zajmuje się konwersją formatów oraz innymi kwestiami niefunkcjonalnymi (np. bezpieczeństwo). Oczywiście stwierdzenie "jeden centralny punkt" dotyczy architektury logicznej. Fizycznie Broker może być zrealizowany za pomocą klastra maszyn w celu zapewniania zwiększonej wydajności i/lub niezawodności.

Wzorzec Message Bus opiera się na braku wspomnianego centralnego punktu. Każdy system (usługa) biorąca udział w komunikacji stanowi węzeł abstrakcyjnej "szyny informacyjnej". Skoro brak jest centralnej koordynacji, to czym Message Bus różni się od tak znienawidzonego spaghetti? Już spieszę z odpowiedzią -- wspólnym mechanizmem komunikacji. Każdy węzeł szyny komunikuje się zegodnie ze wspólnym, uzgodnionym protokołem. Dzięki temu dodanie kolejnego systemu do układanki nie powoduje lawinowego wzrostu skomplikowania systemu.

Dlaczego o tym piszę? Otóż dlatego, że NServiceBus jest frameworkiem pozwalającym budować systemy (lub systemy systemów;-) zgdonie z wzorcem Message Bus. Często wyobrażamy sobie szynę informacji (nazywaną czasem ESB), jako ogromny kawał infrastruktury, który kupujemy za ciężkie pieniądze od IBM czy Oracle. Takie ESB stoi sobie na dedykowanych maszynach w dedykowanej serwerowni... Tylko czy w tym dzikim pędzie za kolejnymi ficzerami i trzyliterowymi skrótami nie zapominamy o samej idei Message Bus? Brak centralnych punktów, tak? Hmm...



NServiceBus pozwala zrealizować ten jeden, konkretny system, nad którym właśnie pracujesz tak, aby komunikacja odbywała się w topologii Message Bus. W NServiceBus każdy niezależny proces systemu to węzeł Szyny. Wszystko, czego potrzeba, aby wpiąć się w tę szynę, to dołączyć referencję do NSB i wystartować swoją instancję IBus.



Na powyższym obrazku klocki ze strzałkami reprezentują kolejki MSMQ. To one (i tylko one) budują szynę. To, czego obrazek nie oddaje to fakt, że kolejki te zlokalizowane są na tych samych fizycznych maszynach, na których systemy je wykorzystujące. Obrazki starają się oddać ważność elementów składowych rozwiązania w obu koncepcjach. Według klasycznej, ESB jest najważniejszym elementem architektury. Według tej, jaką można zrealizować za pomocą NServiceBus, najważniejsze są systemy biznesowe.

opublikowano 20 listopada 2009 16:05 przez simon | 1 komentarzy

Filed under:

NServiceBus - możliwości
Zakładam, że po przeczytaniu poprzedniej notki albo się z zgadzacie, ale przestaliście czytać tę serię. Dlatego pozwolę sobie przejść od razu do konkretów. Wiemy już co-nieco o tym czym jest NSB i jakie są jego główne założenia. Postaram się teraz opowiedzieć dokładniej o tym, co biblioteka ta oferuje programistom.

Komunikacja kolejkowa

NServiceBus posiada warstwę abstrakcji oddzielającą rdzeń biblioteki od szczegółów implementacyjnych mechanizmu transportowego. Dzięki temu istnieje możliwość wyboru alternatywnego, w stosunku do MSMQ, sposobu komunikacji, np. opartego bezpośrednio na UDP lub (nowość!) na kolejkach Windows Azure. Niemniej jednak MSMQ pozostaje wciąż najbardziej popularnym rozwiązaniem, dlatego też ten tutorial będzie mocno zorientowany na ten transport.

W NServiceBus każdy węzeł szyny posiada 2 kolejki: kolejkę wejściową oraz kolejkę błędów. NSB w nieskończonej pętli odbiera i przetwarza komunikaty z kolejki wejściowej. Jedna instancja szyby może obsługiwać tylko jedną taką kolejkę. Komunikaty, których nie udało się przetworzyć pewną założoną ilość razy (domyślnie 5) trafiają do kolejki błędów (dead-letter queue), gdzie oczekują na manualne przetworzenie przez jakiegoś administratora.

Wysyłanie komunikatu w NServiceBus polega na wstawieniu go do kolejki wejściowej adresata. To bardzo ważne. Nie istnieje nic takiego jak kolejka wyjściowa. Zamiast tego komunikat od razu trafia na wejście kolejki docelowej. Jeśli kolejka ta znajduje się na innej maszynie, lokalna infrastruktura MSMQ automatycznie zajmuje się forwardowaniem komunikatu do docelowej instancji MSMQ.


Publikacja polega na wstawieniu komunikatu do wejściowej kolejki odbiorców.
Własna kolejka wejściowa jest pomijana


Wysyłanie komunikatów

Wysyłanie komunikatów w NSB udostępnione jest za pośrednictwem wszechobecnego interfejsu IBus. Dlaczego wszechobecnego? Dlatego, że IBus dostępny jest z każdego miejsca w kodzie (można go po zainicjowaniu na starcie aplikacji zachować w jakiejś statycznej zmiennej). IBus jest bezpieczny, jeśli chodzi o wywołania z wielu wątków i zachowuje się różnie w zależności od tego, jakim kontekście został użyty. Bardzo rzadko przyjdzie wam schodzić niżej, niż poziom tego mega-interfejsu. A wracając do tematu -- IBus udostępnia szereg metod służących do wysyłania komunikatów. Można wśród nich wyróżnić dwie główne grupy: Send oraz Publish.

Metoda Send służy do wysłania komunikatu (lub zestawu komunikatów) do jednego konkretnego adresata. Adresat ten (w postaci nazwy kolejki) może być podany jako argument lub pobrany z konfiguracji. Preferowane jest rozwiązanie wykorzystujące konfigurację. W sekcji MsmqTransportConfig określamy mapowanie assembly lub typ -> kolejka docelowa.

Metoda Publish służy do wysłania komunikatu do wielu odbiorców jednocześnie. Odbiorcy ci muszą jednak wcześniej zgłosić zapotrzebowanie na dostarczanie komunikatów danego typu.


Proces subskrypcji: 1) wysłanie komunikatu,
2) zapisanie informacji o subskrypcji w bazie danych


Zgłoszenie to określa się mianem subskrypcji. Subskrypcje są przechowywane wewnętrznie przez NSB. Aby zasubskrybować dany komunikat, klient wywołuje na swojej instancji IBus metodę Subscribe, której działanie polega na wysłaniu systemowego komunikatu subskrypcji na adres serwera. Oznacza do, że klient musi w swojej sekcji MsmqTransportConfig posiadać mapowanie typu subskrybowanego komunikatu na adres kolejki wejściowej serwera.

To be continued...

opublikowano 19 listopada 2009 18:13 przez simon | 0 komentarzy

Filed under:

NServiceBus
W wielu moich dotychczasowych postach mieliście okazję poczytać o bibliotece NServiceBus. Jest nawet osobna kategoria o tej nazwie w chmurze tagów. Odnoszę jednak wrażenie, że nie poświęciłem wystarczająco dużo czasu, aby Wam przedstawić NSB. Dopiero teraz zdałem sobie sprawę, że nie jest to biblioteka powszechnego użytku, jak np. NUnit czy NHibernate.

NServiceBus jest produktem niszowym. Nie aspiruje do bycia kombajnem od wszystkiego. Ma swój jeden, bardzo dobrze zdefiniowany cel: jest frameworkiem do komunikacji asnychronicznej z obsługą publish/subscribe. Tylko tyle, a jednocześnie aż tyle. Samo nasuwa się porównanie do WCF: zakres odpowiedzialności NSB to niewielki ułamek tego, co robi WCF. Z drugiej strony, w ramach tego niewielkiego zakresu NServiceBus potrafi o wiele, wiele więcej niż WCF.

Aby wejść w świat NSB zmuszeni jesteśmy porzucić kilka wygodnych abstrakcji. Po pierwsze, komunikacja pomiędzy dwoma procesami/usługami/aplikacjami zawsze wiąże się z niebanalnym narzutem. Nie więc możemy udawać, że to tylko wywołanie metody. Komunikacja między dwoma procesami realizowana jest przez przesyłanie komunikatów, a nie wywołanie metod.

Zapomnijmy także o tym, że możemy bez konsekwencji wymienić warstwę transportową zmieniając wyłącznie binding. Każdy, kto zna lepiej WCF-a wie, że to tylko iluzja i że macki binding-ów (w postaci wielu subtelnych niuansów) sięgają daleko głębiej niż się z początku wydaje i zmiana transportu bez wcześniejszego przygotowania jest nierealna.

Na koniec zapomnijmy o czymś takim, jak transakcje typu ACID między różnymi procesami/aplikacjami. Transakcje rozproszone to kolejna z cieknących abstrakcji odziedziczonych po systemach komponentowych (CORBA?). Nie dowiemy się jednak tego wcześniej, niż przy pierwszym produkcyjnym wdrożeniu systemu, który masowo z nich korzysta. Problem z transakcjami między procesami jest taki, że tworzą one bardzo silne powiązanie (także czasowe) między zaangażowanymi stronami. Cierpi na tym zarówno dostępność (bo dostępność systemu z transakcjami rozproszonymi jest iloczynem dostępności jego elementów składowych), jak i wydajność (bo awaria jednego komponentu powoduje utratę rezultatów całej transakcji).

Czytelniku, jeśli dotarłeś aż tutaj stosując się do sugestii autora, jesteś gotów na rozpoczęcie przygody z NServiceBus. NSB wymaga bowiem (w odróżnieniu od WCF), aby zaakceptować rzeczywistość taką, jaka ona jest.

W następnych odcinkach postaram się pokazać, jak NServiceBus pozwala stworzyć system, który nie walczy z prawami fizyki, ale żyje z nimi w zgodzie i harmonii.

opublikowano 18 listopada 2009 17:42 przez simon | 7 komentarzy

Filed under:

DDD i breakthrough refactoring
Są takie momenty w życiu modelu domeny, kiedy niewinna, z pozoru, refaktoryzacja powoduje przełomowe zmiany w jego strukturze i zachowaniu. Jest to zjawisko, które (wg Erica Evansa) jest ostatecznym celem procesu knowledge crunching będącego istotą DDD.

Jakiś tydzień temu byłem uczestnikiem właśnie takiego zjawiska. Moduł Zabezpieczeń, nad którym od jakiegoś czasu się pracowaliśmy zajmował się przetwarzaniem zabezpieczeń dla różnych obiektów biznesowych: ofert, transakcji, grafików. Nie miało to jednak żadnego odzwierciedlenia w jego strukturze, no może poza obiektem Zabezpieczenia, który był połączony ze wspomnianymi za pomocą relacji wiele-do-jeden. Obiekt ten nie miał jednak żadnego zachowania.

Wszelkie procesy biznesowe zrealizowane były jako metody warstwy usługowej wykonujące "magiczne" operacje na kolekcjach zabezpieczeń oraz jednocześnie wysyłające odpowiednie komunikaty do zewnętrznego systemu transakcyjnego. Inne metody przetwarzały komunikaty zwrotne aktualizując stan listy obiektów reprezentujących zabezpieczenia.

Impulsem dla zmian było moje pragnienie przeniesienia wspomnianej logiki na poziom modelu. Przełomem zaś okazało się być zamknięcie grupy Zabezpieczeń związanych z jednym obiektem w obiekt KolekcjaZabezpieczeń. Obiekt ten, mimo iż nie jest encją, natycmiast stał się centralny dla całego modułu. Okazało się, że za pomocą jednynie 3 operacji:
  • Take - zlecenia zmniejszenia zabezpieczeń
  • Modify - zlecenia modyfikacji zabezpieczeń
  • ApplyChanges - uwzględnienia odpowiedzi systemu zewnętrznego
udało się wyrazić najważniejsze procesy dziejące się w module. Wszystkie wieloekranowe metody warstwy usługowej udało się zastąpić wywołaniami Taka i Modify drastycznie zmniejszając ilość kodu. Wszystkie metody przetwarzania odpowiedzi udało się zastąpić poejedynczym ApplyChanges. Wypychanie danych do systemu zewnętrznego  zostało zlokalizowane w jednym miejscu -- klasie obsługi zdarzenia domenoego (dzięki, Udi!) "zaktualizowano kolekcję zabezpieczeń" zgłaszanego po każdej modyfikacji KolekcjiZabezpieczeń.

Czyż to nie piękne?

PS. Wniosek na przyszłość: nigdy, przenigdy, nie dopuszczać do tego, aby obiekt modelu domeny posiadał publiczne property typu List. Nie i koniec!

opublikowano 15 listopada 2009 17:01 przez simon | 1 komentarzy

Więcej wypowiedzi Następna strona »