Posts tagged event sourcing

Why do I find Event Sourcing interesting?

What makes Event Sourcing so interesting? I it is not the free, proven, audit trial, nor the possibility of reincarnating object in any particular state it had in the history. It is also not the great performance of add-only event store. It is testability.

Why Event Sourcing makes things testable? By splitting up logic responsible for two distinct things: calculating new state of the object and applying this state change. Normally, these two operations are mixed in one business logic method like this:

public class Customer
{
    public void PlaceOrder(Product product, int quantity)
    {
        CheckProductAvailability();
        CheckCustomerCreditLimit();
        CheckSomethingElse();
        decimal price = CalculateTotalPrice(product, quantity);
        Order o = new Order(product, quantity, price);
        _orders.Add(o);
    }
    //...
}

If you read this carefully, you will see that first 4 lines contain the business logic of state change calculation. The last 2 lines, on the other hand, contain no logic at all. Now, what’s the problem with this code’s testability?

The problem becomes visible, when I want to test how PlaceOrder behaves when adding fifth order. I have to call PlaceOrder four times to prepare state of the object. That is wrong, because my test code invokes a lot of logic during preparation phase. This means that a lot of things can go wrong and nobody likes tests which fail before the actual test code gets executed.

Let’s see if PlaceOrder can be corrected using SOLID principles. Does it have exactly one reason to change? It seems like it does. When order placement logic changes, the method changes, right? Let’s dig deeper. What about the last two lines? Do they change when the rest of method changes? Well, I don’t think so. Looks like they indeed do represent a different concern. So let’s refactor them to a separate method.

public class Customer
{
    public void PlaceOrder(Product product, int quantity)
    {
        CheckProductAvailability();
        CheckCustomerCreditLimit();
        CheckSomethingElse();
        decimal price = CalculateTotalPrice(product, quantity);
        ApplyPlaceOrder(product, quantity, price);
    }
    private void ApplyPlaceOrder(Product product, int quantity, decimal price)
    {
        Order o = new Order(product, quantity, price);
        _orders.Add(o);
    }
    //...
}

Now it looks better. At least from the have one reason to change principle. But I don’t see this improved testability at all. That is because although we split the responsibilities to separate methods, we left the methods tightly coupled to one another. Lets see what we can do about this.

public class Customer
{
    public void PlaceOrder(Product product, int quantity)
    {
        CheckProductAvailability();
        CheckCustomerCreditLimit();
        CheckSomethingElse();
        decimal price = CalculateTotalPrice(product, quantity);
        Apply(new OrderPlacedEvent(product, quantity, price));
    }
    private void OnOrderPlaced(OrderPlacedEvent @event)
    {
        Order o = new Order(@event.Product, @event.Quantity, @event.Price);
        _orders.Add(o);
    }
    //...
}

We introduced a concept of event as a way of encapsulating the state change we are about to apply. Now we can represent state changes by a series of events. The other thing we did was adding the Apply method. What is it? The Apply method is the API of Event Sourcing. It is a way of communicating hey, I, the aggregate root, want to apply the state change represented by this event! Apply invokes a method to apply state change. It can do this in a variety of ways: based on convention (like On… convention in this sample), an attribute or any other. The bottom line is, Apply applies the state change and it can be called externally, for example during tested state preparation.

[TestFixture]
public class CustomerTests
{
    [Test]
    public void When_placing_fifth_order()
    {
        Product product = new Product();
        //Arrange
        Customer c = new Customer();
        c.Apply(new OrderPlacedEvent(product, 1, 10));
        c.Apply(new OrderPlacedEvent(product, 1, 10));
        c.Apply(new OrderPlacedEvent(product, 1, 10));
        c.Apply(new OrderPlacedEvent(product, 1, 10));

        //Act
        c.PlaceOrder(product, 1);

        //Assert...
    }
}

You can see that we invoke no business logic during arrange phase. We only apply predefined, known to be right, state changes. The chance something bad will happen during this phase is minimal, as we certainly tested the OnOrderPlaced method in a different test, didn’t we?

This is what I call well tested code. PlaceOrder method is always tested in isolation, no matter if it is first or fifth order. One cannot overestimate the benefits of a testable codebase. If you happen to play (or work) with Domain-Driven Design, Event Sourcing can be beneficial for you. I see it as the missing piece of puzzle — the model is finally fully testable.

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

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)

Event Sourcing

Ponieważ temat Event Sourcing stał się w ostatnich miesiącach bardzo popularny (głównie za sprawą Grega Younga) chciałbym także i ja coś o nim napisać. Coś w rodzaju, powiedzmy, wprowadzenia w zagadnienie w formie odpowiedzi na kilka prostych pytań.

Co to?

Event Sourcing to wzorzec projektowy (dokładny opis znajdziecie u Fowlera), który polega na przechowywaniu stanu obiektów w formie serii zdarzeń reprezentujących modyfikacje. Oznacza to, że nigdzie (poza pewnymi wyjątkami, ale o tym później) nie jest przechowywany aktualny stan obiektu. Stan ten, w razie potrzeby, jest odtwarzany poprzez naniesienie na “czysty” obiekt wszystkich modyfikacji, począwszy od początku świata.

Po co to?

Najważniejszym zyskiem z Event Sourcingu to wbudowanie w system mechanizmu audytowego, który gwarantuje spójność danych transakcyjnych i audytowych… ponieważ są to te same dane. Reprezentacja zdarzeniowa pozwala odtworzyć stan dowolnego obiektu w dowolnej chwili czasu, co w niektórych systemach może być bardzo przydatne. Aby nie być gołosłownym powiem tylko, że sam implementowałem (2 razy…) różnicowy mechanizm zapisu zmian danych w formie dokumentów XML, aby umożliwić podgląd stan obiektu w dowolnej chwili jego życia.

Drugą zaletą jest możliwość szybkiej i bezproblemowej modyfikacji modelu danych wdrożonego i utrzymywanego systemu. Event Sourcing bywa zwykle wykorzystywany w połączeniu z CQRS. W takim wypadku mamy część transakcyjną oraz część raportową (dla celów zapytań). ES wykorzystujemy oczywiście w części transakcyjnej. Powszechnym sposobem przechowywania zdarzeń jest zwykła serializacja. Gdy rozbudujemy zdarzenie o nowe pole, w zależności od zastosowanego mechanizmu serializacji możemy:

  • tak zmodyfikować obiekt, aby obsługiwane były zarówno zdarzenia z, jak i bez, nowego pola
  • stworzyć konwerter, który w runtime podczas podczas odtwarzania obiektu będzie transformował zdarzenia zapisane w starszych wersjach na wersję najnowszą.W takim wypadku obiekt musi umieć radzić sobie jedynie z najnowszą wersją.

Tak czy inaczej, jeśli wykorzystujemy bazę danych do przechowywania zdarzeń (jest to całkiem niezły pomysł), modyfikacja modelu nie wiąże się z modyfikacją schematu bazy transakcyjnej. A co z bazą raportową? Możemy ją po prostu wyrzucić i wygenerować od podstaw przetwarzając cały zapisany od początku świata strumień zdarzeń. Greg twierdzi, że taka operacja nawet dla dużych systemów może być zrealizowana w nocnym oknie serwisowym.

Jak to zrobić?

Można na przykład tak lub tak. Pierwsza implementacja to dzieło Marka Nijhofa, bodaj pierwszy publicznie dostępny duży kawał kodu demonstrujący koncepcje Event Sourcing oraz CQRS. Druga, to moja własna, wykonana w ramach rozbudowy kodu DDDSample.Net. Generalnie, podstawowa implementacja Event Sourcing jest banalnie prosta. Udowodnię to twierdzenie na przykładzie.

Po pierwszy potrzebujemy klasy bazowej dla obiektów przechowywanych w ten sposób. To najprostsza metoda. Można oczywiście próbować stworzyć mechanizm Event Sourcing dla PONO, ale w tym wypadku chyba gra nie jest warta świeczki. Moja klasa bazowa (AggregateRoot) ma trzy ważne metody. Pierwsza z nich to Publish.

protected void Publish<TAggregate, TEvent>(TAggregate @this, TEvent @event)
   where TAggregate : AggregateRoot
   where TEvent : Event<TAggregate>
{
   Apply(@event);
   ((IAggregateRoot)this).Events.Add(@event);
   Bus.Publish(@this, @event);
}

Modyfikuje ona stan obiektu za pomocą przekazanego zdarzenia i jednocześnie publikuje to zdarzenie za pomocą jakiejś szyny — udostępnia je na zewnątrz. Widzimy tutaj wywołanie Apply. Co robi Apply?

private void Apply(object @event)
{
   if (@event == null)
   {
      throw new ArgumentNullException("event");
   }
   string eventTypeName = @event.GetType().Name;
   int suffixIndex = eventTypeName.LastIndexOf("Event");
   if (suffixIndex <= 0)
   {
      throw new InvalidOperationException("Invalid event name: " + eventTypeName);
   }
   string methodName = "On" + eventTypeName.Substring(0, suffixIndex);
   MethodInfo methodInfo = GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
   methodInfo.Invoke(this, new[] { @event });
}

Metoda ta wykorzystuje konwencję

“On” + nazwa typu zdarzenia – suffix “Event”

do znalezienia w obiekcie metody obsługi zdarzenia. Znaleziona metoda jest wywoływana. Jak wygląda praktyczne wykorzystanie? Oto fragment klasy Cargo:

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

   Publish(this, new CargoDestinationChangedEvent(routeSpecification,
                                          _deliveryStatus.Derive(routeSpecification, _itinerary)));
}
//...
private void OnCargoDestinationChanged(CargoDestinationChangedEvent @event)
{
   _routeSpecification = @event.NewSpecification;
   _deliveryStatus = @event.Delivery;
}

Metoda biznesowa kończy się modyfikacją stanu obiektu (wywołanie Publish w linii 10). Do zdarzenia dopasowywana jest następnie metoda (OnCargoDestinationChanged), która jest wywoływana celem dokonania modyfikacji stanu. Ostatnim elementem układanki jest odtworzenie stanu. Nic prostszego.

void IAggregateRoot.LoadFromEventStream(IEnumerable<object> events)
{
   foreach (object @event in events)
   {
      Apply(@event);
   }
}

To załatwia nam jednak dopiero obsługę zdarzeń po ich odczycie z trwałego magazynu. Aby móc z tego skorzystać potrzebujemy w ogóle możliwości zapisu i odczytu tych zdarzeń. Jak już pisałem, dobrym wyjściem w niekrytycznych wydajnościowo systemach jest użycie relacyjnej bazy danych. Ja, dla większej łatwości użyłem dodatkowo NHibernate, ale tylko dlatego, że bardzo mi się spieszyło. Moje zdarzenie wygląda tak:

public class Event
{
   public int SequenceNumber { get; set; }
   public int Version { get; set; }
   public Guid EntityId { get; set; }
   public object Data { get; set; }
   public bool IsSnapshot { get; set; }
}

Posiada numer kolejny (do sortowania), numer wersji (do optymistycznej kontroli zmian), ID obiektu, zserializowane dane zdarzenia oraz flagę “snapshot”. O snapshotach wspomnę w ostatniej części tej notki. Jakie operacje musimy wykonać na takim obiekcie? Po pierwsze zapis do bazy nowego zdarzenia. Banalnie proste. Po drugie odczyt pełnej serii zdarzeń dotyczących obiektu o podanym ID, w kolejności od najstarszego. To zapytanie również nie powinno nastręczać zbyt wiele problemów.

To wszystko. Dokładnie tyle potrzeba do realizacji własnego mechanizmu Event Sourcing. Prawda, że łatwe?

Jak to zoptymalizować?

Zapewne uważny czytelnik zwrócił uwagę na kilka potencjalnych problemów wydajnościowych związanych z ES. Po pierwsze, odtwarzanie zdarzeń “od początku świata” za każdym razem gdy potrzebujemy obiektu może być bardzo czasochłonne. Dlatego dopuszcza się istnienie, oprócz normalnych zdarzeń, tzw. snapshotów. Są to zrzuty kompletnego obiektu zserializowanego w całości — wyjątek od reguły “przechowywania tylko modyfikacji”, o którym wspominałem we wstępie. Mogą występować np. co 10 zwykłych zdarzeń. Dzięki temu odtwarzając obiekt sięgamy po zdarzenia nie od początku świata, ale tylko do najnowszego snapshotu. Sprytne, prawda?

Kolejna sprawa to serializacja. Standardowa, wbudowana we framework, nie jest zoptymalizowana pod kątem wydajności. Warto przyjrzeć się temu porównaniu wybierając własny mechanizm serializacji dla ES.

Co dalej?

Zachęcam do eksperymentów z Event Sourcing. Nie twierdzę, że wszyscy powinniście ES od razu stosować w systemach produkcyjnych. Warto jednak znać możliwości (oraz ograniczenia) tego wzorca. Być może pewnego dnia okaże się przydatny?

VN:F [1.8.7_1070]
Rating: 5.0/5 (3 votes cast)