// wszystkie materiały zostały zebrane w podsumowaniu cyklu.

Wydawać by się mogło, że architektura zdarzeniowa nie będzie cierpiała na problemy braku kontekstów. Moduły aplikacji komunikują się zdarzeniami. W jednym miejscu informujemy o dokonaniu pewnej akcji, a reagujemy na nią w innym miejscu. Dzięki temu mamy większą separację modułów od siebie i lepszy rozkład odpowiedzialności. Prawda?

O wy naiwni!

Problem

Załóżmy, że w module Sklepu, po zakupie produktu rzucasz zdarzenie ProductWasSold Zawiera ono następujące pola:

  • Id
  • Name
  • Price

Integrują się z nim 2 moduły – Zamówień i Dostaw – które obsługują dodanie zamówienia i stworzenie dostawy w reakcji na zakup produktu. Funkcjonalności są od siebie oddzielone – każdy moduł spełnia swoje potrzeby.

Następnie pojawia się potrzeba biznesowa – chcemy rozdzielić cenę na netto i podatek VAT. Dokonujemy takiej zmiany w naszym module, testujemy nasze rozwiązanie – problem. System przestaje działać, bo moduły Zamówień i Dostaw nie akceptują naszych zmian.

Po konsultacjach okazuje się, że musimy wycofać nasze zmiany, bo zaadaptowanie całego systemu do kształtu nowego zdarzenia jest zbyt kosztowne. I co teraz?

Chcieliśmy uciec od piekła integracji przez bazę danych – mamy złączenie na poziomie wydarzeń.

Zdarzenia jako bottleneck

Ten problem został już zauważony w społeczności programistycznej. Jimmy Bogard pisał na Twitterze:

exposing your event streams to the outside world is no different than handing out your SQL connection string. you’re encouraging coupling

Również ThoughtWorks na swoim Technology Radarze pisali, że widzą trend tworzenia centralnego zarządzania zdarzeniami na poziomie całej aplikacji. I opisują związane z tym problemy.

Takie zachowanie tworzy ogromne powiązania pomiędzy poszczególnymi modułami systemu. Nie jesteśmy w stanie zmodyfikować kształtu danego zdarzenia – jest ono używane w zbyt wielu miejscach. Ostatecznie kończymy z systemem, w którym jakakolwiek zmiana jest bardzo kosztowna i wymaga wielu synchronizacji pomiędzy wszystkimi członkami danego projektu.

Rozwiązanie

Rozwiązaniem jest jasny podział na te zdarzenia, które są dostępne tylko wewnątrz naszego modułu i te, którymi dzielimy się na zewnątrz. Czyli na tzw. zdarzenia domenowe i integracyjne. Pisał o nich Jimmy jako rozwinięcie swojego tweeta, a ciekawą implementację można znaleźć na stronach Microsoftu (zdarzenia domenowe / integracyjne).

Taki sposób działania pozwala ukryć nasze wewnętrzne zmiany przed światem zewnętrzym. Możemy modyfikować nasz sposób działania – dodawać nowe zdarzenia, zmieniać istniejące, usuwać niepotrzebne – o ile nasz kontrakt jest utrzymywany.

W naszym powyższym przypadku moglibyśmy mieć 2 różne zdarzenia, np.

  • ProductWasSold
  • ProductWasSoldIntegrationEvent

Pierwsze zdarzenie byłoby rzucane wewnętrznie. Następnie moduł chwytałby to zdarzenie i zmieniał je w zdarzenie integracyjne, obsługując zmianę informacji. Dzięki temu, nawet jeśli zdarzenie domenowe by się zmieniło, to na nas byłby jedynie obowiązek, by zapewnić ciągłość pomiędzy oboma typami zdarzeń.

A może inaczej?

Sławek Sobótka, na swojej prezentacji DDD Q&A, mówił o innym rozwiązaniu tego problemu. Polecał on po prostu, by wewnątrz jednego kontekstu nie rzucać zdarzeń. Zmiany w kilku agregatach mogłyby się odbywać wewnątrz serwisu domenowego, który dbałby, by oba obiekty się zmieniły. Nie potrzebowalibyśmy wtedy zdarzeń wewnątrz modułowych.

Jest to pewnie rozwiązanie części problemów, w części sytuacji jednak może to nie dać rady. Jak zwykle – to zależy 😉