Posts tagged CQRS

Ncqrs

W ciągu ostatnich dwóch tygodni moją uwagę przykuł na dobre nowy framework open source — Ncqrs (witryna CodePlex Ncqrs znajduje się tutaj). Jak sugeruje nazwa, Ncqrs służy do budowy systemów w oparciu o wzorzec architektoniczny Command-Query Responsibility Separation (CQRS). To, czego nazwa nie mówi, to fakt, że Ncqrs narzuca pewną specyficzną implementację wspomnianego wzorca, a mianowicie tę opartą o technikę Event Sourcing. Na podstawie posta Grega Younga można by się czepiać, że nazwa Ncqrs nie jest zbyt trafna, ale odłóżmy kwestie nomenklatury na bok. Czym jest Ncqrs i jak to się stało, że mnie tak zafascynował?

Zasada działania

Ncqrs jest całościowym rozwiązaniem służącym do budowy systemów opartych o silny model domeny, którego stan jest przechowywany za pomocą strumienia zdarzeń. Poniższy diagram prezentuje workflow dla pojedynczego przypadku użycia w Ncqrs.

Komendy

Punktem wejścia do Ncqrs są komendy. Są to obiekty, który reprezentują żądania wykonania pewnej operacji na modelu domeny. Komendy są mapowane na operacje za pomocą rozbudowanego rozszerzalnego mechanizmu. Najprostsza implementacja mappera opiera się na dwóch atrybutach, które określają, czy dana komenda ma wykonywać metodę istniejącego obiektu (w nomenklaturze Ncqrs — korzenia agregatu), czy też tworzyć nowy obiekt. Properties komendy są mapowane (na podstawie nazwy) do parametrów wybranej metody lub konstruktora. Ostatecznie, (jeśli to konieczne) z magazynu danych podnoszony jest odpowiedni obiekt i wykonywana jest odpowiednia metoda (lub konstruktor).

Operacje biznesowe

Operacje i konstruktory obiektów są w Ncqrs wywoływane tylko za pośrednictwem komend. Ich jedynym zadaniem jest wykonanie logiki biznesowej. Nie mogą one bezpośrednio modyfikować stanu obiektu. Zamiast tego, dozwolonym mechanizm modyfikacji stanu jest zgłaszanie zdarzeń. Operacje biznesowe mogą także komunikować się ze światem zewnętrznym.

Stosowanie zdarzeń

Jak już wspomniałem na wstępie, zdarzenia są sposobem przechowywania stanu obiektów w Ncqrs. Operacja biznesowa może zgłosić jedno lub więcej zdarzeń. Dla każdego z nich framework wyszukuje odpowiedniej metody je przetwarzającej. Mechanizm ten jest oczywiście rozszerzalny, a out-of-the-box Ncqrs zapewnia dwa sposoby wiązania zdarzenia z metodą obiektu biznesowego przeznaczoną do jego obsługi: za pomocą konwencji oraz za pomocą atrybutów. Ncqrs wywołuje znalezioną metodę przekazując jej zgłoszone zdarzenie.

Przetwarzanie zdarzeń

Metoda przetwarzająca zdarzenie modyfikuje stan obiektu na podstawie danych przekazanych zdarzeniu. Tylko tyle i aż tyle. Metoda ta nie powinna zawierać żadnej logiki biznesowej (warunkowej), ani mieć jakichkolwiek skutków ubocznych (komunikacja z innymi systemami itp.).

Zapewne chcielibyście zapytać po co tyle komplikacji? Dlaczego operacja biznesowa nie może zmodyfikować stanu? Odpowiedź jest prosta. Ponieważ stan obiektów jest reprezentowany jako strumień zdarzeń przez nie wygenerowany, aby odtworzyć obiekt niezbędne jest stworzenie jego pustej instancji, a następnie przetworzenie (w kolejności!) wszystkich zapisanych zdarzeń — oczywiście za pomocą odpowiednich metod przetwarzających. Metody te są więc nie tylko stosowane do modyfikacji stanu podczas przetwarzania, ale także do odtwarzania tego stanu podczas podnoszenia obiektu z trwałego magazynu.

Dzięki takiemu podejściu systemy Event Sourcing (jak Ncqrs) zapewniają, za darmo, ślad audytowy, który ma gwarancję poprawności, ponieważ jest on wykorzystywany do budowy obiektów podczas normalnego działania systemu.

Publikowanie i denormalizacja zdarzeń

Jeśli wszystko do tej pory przebiegło prawidłowo, wszystkie zgłoszone zdarzenia są publikowane. Oczywiście, także w tym wypadku Ncqrs pozwala wymienić mechanizm publikacji zdarzeń. Domyślny wykorzystuje komunikację wewnątrz procesu, ale dostępny jest także taki, który wykorzystuje NServiceBus.

Publikowanie zdarzeń ma dwa cele. Po pierwsze, pozwala powiadomić zainteresowane systemy zewnętrzne o zmianach stanu naszego systemu. Polega to na eskalowaniu “lokalnych” (dotyczących naszego systemu) zdarzeń do statusu zdarzeń “globalnych” (mających znaczenia dla całego środowiska systemów). Stąd już tylko jeden krok do pełnej Event Driven Architecture (EDA).

Drugim celem publikowania zdarzeń jest tzw. denormalizacja, czyli aktualizacja podsystemu obsługi zapytań. Jaki podsystem? O co chodzi? Dokładny opis zagadnienia CQRS znajduej się tutaj. W tym miejscu wspomnę tylko, że systemy CQRS wykorzystują zwykle dwa osobne magazyny danych dla przetwarzania komend oraz do realizacji zapytań. Do synchronizacji tego drugiego magazynu danych wykorzystywane są właśnie denormalizatory zdarzeń. Proces denormalizacji polega na wykonaniu w bazie danych dla zapytań modyfikacji, które wynikają z opublikowanego zdarzenia. Skąd taka nazwa? Otóż zdarzenia stanowią znormalizowaną (pozbawioną redundancji) postać danych. W magazynie dla zapytań zaś, te same dane mogą mieć wiele reprezentacji, ponieważ nadrzędnym celem jest optymalizacja czasu realizacji zapytań.

Dlaczego to może działać?

Jest kilka powodów, które sprawiają, że (pozornie) szalona idea reprezentacji stanu obiektów jako ciągu zdarzeń może działać w praktyce. Oto kilka z nich:

  • zapewnia darmowy, gwarantowany, ślad audytowy
  • możliwość wykonywania tzw. snapshot’ów (czyli pełnych zrzutów zserializowanego obiektu biznesowego) co N zdarzeń. Dzięki temu odtwarzanie obiektu wymaga jedynie przetworzenia zdarzeń, które nastąpiły po ostatnim snapthot’cie
  • w klasycznym DDD (z użyciem O/RM) podczas podnoszenia obiektu z bazy danych tak naprawdę pobierane jest wiele wierszy danych (np. za pomocą podzapytań i złączeń). W przypadku Event Sourcingu pobieranych jest kilka wierszy przechowujących zdarzenia. Oba podejścia mają więc podobną złożoność na poziomie bazy danych.

Zapraszam Was do zabawy z Ncqrs. Postaram się odpowiedzieć na wszystkie Wasze pytania.

VN:F [1.8.7_1070]
Rating: 0.0/5 (0 votes cast)

Raportowanie a CQRS

Zdaję sobie sprawę, że tytuł jest ogromnym skrótem myślowym. Tak naprawdę chodzi mi o możliwość generowania raportów z rozwiązań wykorzystujących model domeny z CQRS. Oczywiście, posłużę się przykładem DDDSample. Na wstępnie jednak muszę się przyznać, że nie jestem ekspertem od business intelligence, więc jeśli popełniłem jakieś karygodne wykroczenia, proszę o wyrozumiałość i zgłoszenie poprawek w komentarzach — będę wdzięczny za wszelkie uwagi.

Problem wygląda następująco: nasz system transakcyjny działa świetnie, jest wydajny, bezpieczny itp. Nadszedł jednak czas, aby wygenerować z niego jakiś raport potwierdzający, że system się sprawdza biznesowo. Jak to zrobić, mając po stronie komend silnie znormalizowaną bazę zoptymalizowaną dla operacji update/insert, a po stronie zapytań — tabele odzwierciedlające formatki UI? Są dwie możliwości:

  1. Zmodyfikować bazę strony zapytań, aby stała się prawdziwą “bazą raportową”. Dzięki temu będzie mogła służyć i do generowania raportów i do wyświetlania danych na formatki. Niestety konsekwencją tego jest znaczne zwiększenie skomplikowania kodu dostępu do danych dla formularzy i list. Dlaczego? Zaraz zobaczymy.
  2. Dodać kolejną (3!) bazę danych zoptymalizowaną pod kątem raportów. Minusem jest, jak zwykle, zwiększony wysiłek ze strony administratorów. Plus (również jak zwykle): lepszy podział odpowiedzialności i możliwość optymalizacji.

Spróbujmy więc zrealizować to drugie rozwiązanie. Na początek potrzebujemy struktury bazodanowej, która nadawałaby się do generowania raportów. Oczywiście, w rzeczywistej sytuacji zapytalibyśmy naszego klienta, jakich raportów potrzebuje. Niestety nie mam takiej możliwości pisząc te notkę. Przygotowałem więc schemat, który wydaje mi się użyteczny:

Pozwala on na analizę towarów pod kątem zmian rzeczywistej trasy w stosunku do tej zarejestrowanej i ich wpływu na dostarczenie towaru. Z drugiej strony umożliwia także analizę samych tras: którędy prowadziła, czy wynikiem było złe skierowanie towaru, czy została porzucona (zmieniona), czy też w wyniku jej realizacji towar został dostarczony.

Na pierwszy rzut oka widać, że transformacja tak przechowywanych danych do formatu niezbędnego do wyświetlenie na formatkach byłoby bardzo trudna. Utwierdza mnie to w przekonaniu, że (tym razem) słusznie wybrałem wariant drugi (osobne bazy).

Mając już strukturę danych, zastanówmy się, czego potrzebujemy, aby ją zasilić? Z pomocą przychodzi nam naturalna właściwość tego rodzaj systemów CQRS. Są one naturalnie przystosowane do scenariuszy real-time business intelligence, ponieważ posiadają wbudowany mechanizm wypychania danych z bazy transakcyjnej w czasie rzeczywistym (służący do zasilania bazy dla zapytań). Możemy się więc w niego bezwstydnie wpiąć. Potrzebujemy jedynie czterech obiektów obsługi komunikatów dla zdarzeń:

  • zarejestrowano towar (utworzenie obiektu w CargoFacts i RouteFacts)
  • przypisano trasę (aktualizacja RouteFacts)
  • zmieniono lokalizację docelową (utworzenie obiektu w RouteFacts, modyfikacja reprezentującego poprzednią trasę)
  • zarejestrowano zdarzenie obsługi (aktualizacja rzeczywistego miejsca załadunku towaru i miejsca dostarczenia towaru, aktualizacja pola “misdirected” itp.)

Implementacje możemy wykonać (znów) na dwa sposoby: albo bezpośrednio przygotowując odpowiednie komendy SQL, albo mapując przedstawiony model na obiekty. Co kto woli.

PS. Zamierzam spróbować zaimplementować przedstawione rozwiązanie. Jak tylko mi się uda, podzielę się z Wami wynikami eksperymentu.

VN:F [1.8.7_1070]
Rating: 0.0/5 (0 votes cast)

Tell, don’t ask

Tell, don’t ask, lub, jak kto woli w oryginale:

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things

— Alec Sharp

to stara zasada projektowania obiektowego mająca swoje odzwierciedlenie także na poziomie architektury. I, o ile na poziomie programowania nikt właściwie jej nie kwestionuje, o tyle na wyższych poziomach abstrakcji propozycje jej wprowadzenia budzą znaczący sprzeciw. Dlaczego? Tego nie wiem.

Głównym powodem stosowania wspomnianej reguły dla kodu obiektowego jest chęć zachowania niskiego poziomu powiązania. Nie powinniśmy uzależniać swojego kodu od wewnętrznej implementacji obiektu, z którego korzystamy. Co więcej, ten drugi obiekt niechętnie eksponuje nam swoje wnętrze za pomocą odpytań, ponieważ czyniąc to sabotuje wszelkie późniejsze modyfikację, gdyż udostępnione szczegóły stają się natychmiast częścią publicznego kontraktu jego API.

Domain-Driven Design sprawdza się najlepiej w przypadku implementacji skomplikowanej logiki, która charakteryzuje się dużą podatnością na zmiany (Udi Dahan w MSDN Magazine). Stosowanie DDD, aby umożliwić szybkie zmiany, a jednocześnie ignorowanie zasady tell, don’t ask (prowadzące do narastania utrudniających wszelkie modyfikację powiązań) wydaje się kompletnie pozbawione sensu! A jednak, wielokrotnie słyszymy, że DDD jest w porządku, ale promujący podejście tell, don’t ask wzorzec CQRS to przerost formy nad treścią.

Powtórzę jeszcze raz to, co świetnie opisał GregCQRS jest zupełnie niezależny od decyzji architektonicznych. CQRS nie wymaga Event Sourcing-u (choć dobrze z nim współgra). CQRS nie wymaga nawet osobnych magazynów danych (choć traci wtedy wiele ze swej mocy). CQRS nie kosztuje praktycznie nic, jeśli już decydujemy się na DDD. Naprawdę nie ma się czego bać!

VN:F [1.8.7_1070]
Rating: 0.0/5 (0 votes cast)

Event sourcing in DDDSample: reviewing Mark Nijhof’s solution

As CQRS version of DDDSample is getting mature, I am switching my development efforts to event sourcing support. For those of you who don’t know that yet, event sourcing is a pattern which encourages persisting not snapshots of data in particular moments in time, but rather events which describe how these data changes. Then, data snapshots (not only the latest, but also historic ones) can be reconstructed by applying these events starting from the ‘beginning of time’. More about defining event sourcing and CQRS can be found here.

I’ve started by looking at Marc Nijhof’s excellent series of posts (starting here) describing event sourcing. Mark has built a sample application based on what he had learnt about CQRS during Greg Young’s course.

Mark’s solution is fairly simple. In the core there is a domain assembly containing classes that model domain concepts like Account and Client. These inherit and use additional infrastructure assemblies which provide event sourcing features.

One thing that looks odd to me is usage of memento pattern to store snapshots of aggregates. I don’t understand why can’t they (the aggregates) be simply serialized as they are. Maybe there is a reason behind this, but I don’t see it, at least for now. I marked this area for further investigation.

Moving away from the core, there are several supporting assemblies which contain commands, command handlers, events and so on. Personally, I don’t see a point in separating some of them. There is an obvious reason why commands and command handlers are in separate assemblies, but the events? They are part of the domain model, at least for me.

After more careful analysis I noticed that there is no notion of messages in Marks sample solution. The events themselves are passed from command side to query side. That is probably the reason why the events are defined in separate assembly — if they are to be used in reporting, it would be nice not to have to reference whole domain model assembly, only the events.

Going further, Mark is using a custom O/RMish solution to implement query/reporting side. I am not a big fan of custom solutions and I probably would use an off-the-shelve one, even if it is slightly more complex then necessary. I am planing to use NHibernate, as in the non-event-sourcing CQRS version.

Anyway, Mark did great job of describing event sourcing concepts to the public. I probably wouldn’t be able to start writing my own implementation in DDDSample without first reading his code. Thanks a lot!

VN:F [1.8.7_1070]
Rating: 3.0/5 (2 votes cast)

CQRS w praktyce

Dużo piszę ostatnio o CQRS (Command Query Responsibility Segregation), ale nie pokazałem ani razu jak to podejście wygląda w praktyce. Postaram się dziś naprawić to niedopatrzenie. Posłużę się w tym celu projektem DDDSample w najnowszej wersji CQRS.

Układ solution

Tak wygląda układ solution Visual Studio:

solution layout

Kod podzielony jest na cztery główne obszary:

  • Domain — tutaj znajduje się logika biznesowa aplikacji, której zadaniem jest przetwarzanie transakcji (komend). Oprócz centralnego projektu “Domain”, w obszarze tym znajdują się dwa wspomagające. Idąc od góry, pierwszy z nich zawiera obsługę zdarzeń domenowych a’la Udi Dahan, drugi zaś kod dostępu do danych (w oparciu o NHiberate)
  • Reporting — ten obszar ma za zadanie obsługiwać zapytania o dane do wyświetlenia na GUI. W razie potrzeby może on także służyć do generowania raportów. Główny projekt (“Reporting”) zawiera definicję struktur danych. Pozostałe to dostęp do danych oraz obsługa komunikatów reprezentujących zdarzenia aktualizacji modelu.
  • Infrastructure — tutaj znajduje się wszelki kod nie związany bezpośrednio z problemem, ale w jakiś sposób wspierający jego rozwiązanie, np. projekt “Messages” zawierający komunikaty przesyłane między podsystemem obsługi komend, a podsystemem obsługi zapytań.
  • W najwyższym poziomie hierarchii, bezpośrednio pod solution znajdują się dwa projekty scalające: “Application” oraz “UI”. Ten pierwszy stanowi fasadę ukrywającą złożoność całego rozwiązania przed tym drugim. “UI” to zwykły interfejs użytkownika wykorzystujący ASP.NET MVC.

Workflow

Skoro już wiecie, jak z grubsza wygląda rozwiązanie, chciałbym teraz omówić zachowanie systemu podczas standardowej interakcji, która przedstawia się tak:

Diagram przepływu CQRSNa powyższym diagramie MDK oznacza Model Domeny dla obsługi komend (projekt “Domain”), a MDZ to model dla raportowania (projekt “Reporting”).

W pierwszym żądaniu użytkownik systemu chce zobaczyć dane związane z obiektem biznesowym. System ładuje je z prosto z relacyjnej bazy danych MDZ. Drugie żądanie to już modyfikacja danych. Jest ona wykonywana na obiektach Modelu Domeny, które następnie są zapisywane w formie danych relacyjnych. Dokonane zmiany są także (w jakiejś formie) publikowane do MDZ, gdzie służą do aktualizacji struktur danych.

Zobaczmy, jak wygląda realizacja tego schematu w aplikacji DDDSample. Posłużę się przykładem komendy “Zmień docelową lokalizację dla towaru”.

Pobranie danych

Operacja pobrania danych zaczyna się od odpowiedniej akcji kontrolera:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult ChangeDestination(string trackingId)
{
   Reporting.Cargo cargo = _bookingFacade.LoadCargoForRouting(trackingId);
   //...

która odwołuje się do fasady:

public Reporting.Cargo LoadCargoForRouting(string trackingId)
{
   Reporting.Cargo c = _cargoDataAccess.Find(trackingId);
   if (c == null)
   {
      throw new ArgumentException("Cargo with specified tracking id not found.");
   }
   return c;
}

Fasada z kolei wykorzystuje bezpośrednio obiekt dostępu do danych:

public Cargo Find(string trackingId)
{
   const string query = @"from DDDSample.Reporting.Cargo c where c.TrackingId = :trackingId";
   return _sessionFactory.GetCurrentSession().CreateQuery(query).SetString("trackingId", trackingId)
      .UniqueResult<Cargo>();
}

Jak widzicie, nie ma tu żadnego przekształcenia danych po ich zwróceniu przez NHibernate. Żadnego DTO. Ponieważ kod podsystemu obsługi zapytań nie jest szczególnie wartościowy, mogę pozwolić sobie na nieco luźniejszą politykę zarządzania zależnościami. Na przykład obiekt dostępu do danych (CargoDataAccess) wykorzystywany jest jako konkretna klasa — nie mam jego abstrakcyjnej definicji. Nie muszę dbać o długowieczność tej części systemu. Kiedy pojawi się lepsza technologia, po prostu wyrzucę całą implementację, począwszy od fasady, aż do samego dołu.

Tak naprawdę użycie NHibernate tutaj może być nawet traktowane jako nadmierne skomplikowanie. Linq2SQL byłoby pewnie lepsze. Ale to temat na osobną notkę…

Wykonanie komendy

Wykonanie komendy także zaczyna się od akcji na kontrolerze. Zwróćcie uwagę, że tym razem akcja jest odpowiedzią na żądanie POST.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ChangeDestination(string trackingId, string destination)
{
   _bookingFacade.ChangeDestination(trackingId, destination);
   return RedirectToDetails(trackingId);
}

Co kryje się za metodą fasady?

public void ChangeDestination(string trackingId, string destination)
{
   _bookingService.ChangeDestination(new TrackingId(trackingId),
      new UnLocode(destination));
}

Przekazujemy sterowanie do warstwy Application (obiekt _bookingService). Zaczyna się właściwe wykonanie komendy. W tym momencie włącza się lekki moduł programowania aspektowego (AOP) w moim kontenerze Unity, który zawija całe wywołanie ChangeDestination w transakcję.

Właściwa implementacja komendy zmiany punktu docelowego wygląda tak:

public void ChangeDestination(TrackingId trackingId, UnLocode destinationUnLocode)
{
   Location destination = _locationRepository.Find(destinationUnLocode);
   Cargo cargo = _cargoRepository.Find(trackingId);

   cargo.SpecifyNewRoute(destination);
}

Pierwszym krokiem utworzenie obiektu reprezentującego nową lokalizację docelową towaru. W tym celu wykorzystujemy (abstrakcyjne) repozytorium lokalizacji. Następnie pobieramy z bazy obiekt towaru, którego docelowa lokalizacja ma być zmieniona. Ostatecznie wywołujemy odpowiednią metodę na obiekcie reprezentującym towar. Wygląda ona tak:

public virtual void SpecifyNewRoute(Location.Location destination)
{
   if (destination == null)
   {
      throw new ArgumentNullException("destination");
   }
   RouteSpecification routeSpecification = new RouteSpecification(_routeSpecification.Origin, destination, _routeSpecification.ArrivalDeadline);

   Delivery delivery = Delivery.DerivedFrom(routeSpecification, _itinerary, _lastHandlingEvent);
   CargoDestinationChangedEvent @event = new CargoDestinationChangedEvent(this, routeSpecification, _routeSpecification, delivery);
   _routeSpecification = routeSpecification;
   DomainEvents.Raise(@event);
}

Pomińmy walidację argumentów. Zaczynamy od stworzenia obiektu reprezentującego nową specyfikację trasy tego towaru. RouteSpecification jest tzw. value object-em, więc nie modyfikujemy istniejącej instancji, ale tworzymy nową. Następnie wykorzystujemy obiekt Delivery do określenia nowych danych dotyczących dostawy towaru. Dane te w kolejne linii są wykorzystywane do utworzenia zdarzenia informującego o zmianie lokalizacji docelowej dla towaru. Na koniec ustawiamy nową specyfikację (a wraz z nią lokalizację docelową) i publikujemy zdarzenie.

Asynchroniczna synchronizacja danych

:-) Fajnie mi się napisało. W klasycznym systemie przetwarzanie byłoby już zakończone, ale nie w CQRS. Teraz ma miejsce ostatni etap, jakim jest uwzględnienie dokonanych zmian do w bazie obsługującej zapytania. Pierwsza faza właściwie już się wykonała — była to publikacja zdarzenia. Kolejna to jego obsługa. Zajmuje się nią następująca klasa (zlokalizowana w projekcie “Domain.EventHandlers”):

public class CargoDestinationChangedEventHandler : IEventHandler<CargoDestinationChangedEvent>
{
   private readonly IBus _bus;

   public CargoDestinationChangedEventHandler(IBus bus)
   {
      _bus = bus;
   }

   public void Handle(CargoDestinationChangedEvent @event)
   {
      _bus.Publish(new CargoDestinationChangedMessage
                      {
                         TrackingId = @event.Cargo.TrackingId.IdString,
                         Origin = @event.NewSpecification.Origin.Name,
                         Destination = @event.NewSpecification.Destination.Name,
                         ArrivalDeadline = @event.NewSpecification.ArrivalDeadline
                      });
   }
}

Jej zadaniem jest zamiana zdarzenia domenowego na komunikat NServiceBus, który jest następnie publikowany “na szynie”. Dalej dzieje się magia NServiceBus i MSMQ, która prowadzi do tego, że wiadomość zostanie obsłużona przez przeznaczony do tego obiekt znajdujący się w projekcie “Reporting.MessageHandlers”:

public class CargoDestinationChangedMessageHandler : AbstractMessageHandler<CargoDestinationChangedMessage>
{
   private readonly CargoDataAccess _cargoDataAccess;

   public CargoDestinationChangedMessageHandler(CargoDataAccess cargoDataAccess, ISessionFactory sessionFactory)
      : base(sessionFactory)
   {
      _cargoDataAccess = cargoDataAccess;
   }

   protected override void DoHandle(CargoDestinationChangedMessage message)
   {
      Cargo cargo = _cargoDataAccess.Find(message.TrackingId);
      cargo.UpdateRouteSpecification(message.Origin, message.Destination, message.ArrivalDeadline);
   }
}

Obsługa komunikatu polega na pobraniu z bazy danych struktury reprezentującej towar, a następnie aktualizacji danych dotyczących specyfikacji trasy.

Podsumowanie

Mam nadzieję, udało mi się nie wystraszyć Was nadmierną ilością kodu. Chciałem pokazać, jak naprawdę wygląda CQRS oraz, że wbrew obiegowej opinii, nie jest to wcale coś strasznego. Nie wymaga wcale większych, niż wykorzystanie wzorca DTO, nakładów pracy, a pozwala na uzyskanie znacznie większej swobody polegającej na względnie niezależnym rozwoju podsystemów obsługi komend i zapytań.

VN:F [1.8.7_1070]
Rating: 3.0/5 (2 votes cast)