W świecie Event-Driven Architecture mamy do czynienia z wysyłaniem wielu zdarzeń. Jednak jak wiemy, jedna rzecz nie zawsze jest równa drugiej pomimo posiadania tej samej nazwy. Dlatego w celu rozróżnienia tych zdarzeń wprowadzono podział na dwa tytułowe rodzaje: Domain Event oraz Integration Event. Powstaje pytanie “z czego on wynika?”. Głównym zamysłem było środowisko, w którym są one propagowane. Przejdźmy zatem do dokładniejszego wyjaśnienia tego podziału.

Czym jest Domain Event?

Według tego co mówią inni deweloperzy to Domain Event obowiązuje w ramach tej samej aplikacji. Oznacza to, że nie wychodzi on poza granice danego procesu. Jednostki, które są zainteresowane danym zdarzeniem, po prostu oczekują aż się ono pojawi i je przetwarzają. Nie ma tutaj żadnych message brokerów czy asynchronicznej komunikacji (chociaż może się ona pojawić). Cała obsługa zdarzeń dzieje się w pamięci.

Takie podejście daje nam niesamowitą możliwość robienia wszystkiego w ramach jednej transakcji bazodanowej. W ten sposób możemy luźno definiować moduły aplikacji i w razie wystąpienia wyjątku natychmiastowo kompensować konsekwencje danego rozkazu.

Przykład propagacji dla Domain Event
Przykład propagacji dla Domain Event

Przy okazji jeśli ktoś były zainteresowany w jaki sposób można publikować Domain Events to zapraszam na blog Kamila Grzybka, gdzie opisuje on 3 takie możliwości.

Oczywiście, jak wspomniałem wcześniej, można również obsługiwać te zdarzenia w sposób asynchroniczny w ramach jednej aplikacji. W przypadku Springa wykorzystujemy wtedy adnotację @Async nad metodą naszego event listenera i voila!. Jednak wybierając tą drogę tracimy zalety jednej transakcji bazodanowej co zostało opisane w dokumentacji.

@Transactional commonly works with thread-bound transactions managed by PlatformTransactionManager, exposing a transaction to all data access operations within the current execution thread. Note: This does not propagate to newly started threads within the method.

Moim zdaniem jeszcze lepiej zostało to zagadnienie wytłumaczone przez użytkownika TriS na StackOverflow. Dobra, ale co z sytuacją, gdy nasza aplikacja musi poinformować inne aplikacje o jakimś zdarzeniu? Wtedy do głosu dochodzi Integration Event.

Czym jest Integration Event?

Jak sama nazwa wskazuje Integration Event służy do integracji z innymi zewnętrznymi jednostkami. W tym przypadku wychodzimy już poza granice naszej aplikacji, więc nie ma mowy o transakcji bazodanowej (przynajmniej prostej). Proces zaczyna wymagać komunikacji asynchronicznej oraz pośrednika w postaci message brokera.

Przykład propagacji dla Integration Event
Przykład propagacji dla Integration Event

Według mnie to dalej są zdarzenia domenowe, więc lepszą nazwą byłaby propozycja dostarczona przez wcześniej wspomnianego Kamila Grzybka, czyli Domain Events Notification. Jednak czegoś takiego nie ma w numaklaturze DDD, a szkoda.

Koncepcja Inside i Outside Event

Dzięki wyżej przedstawionym opisom na myśl sama przychodzi inna propozycja nazewnictwa dla tego podziału. Jak widać są zdarzenia obsługiwane wewnątrz naszej aplikacji oraz poza nią, więc może dobrym pomysłem byłoby korzystanie z nazw Inside i Outside Event.

Koncepcja Inside i Outside Events
Koncepcja Inside i Outside Events

W ten sposób możemy szybko określić, które eventy są nasze, a które eksponujemy innym zespołom na zewnątrz. Można to porównać do koncepcji metod prywatnych oraz publicznych. Nie musimy się o nic martwić jeśli chcielibyśmy zmienić wewnętrzną strukturę Inside Eventu. To jest nasza własność, którą z nikim się nie dzielimy. Natomiast Outside Event powinien być otoczony większą troską. Nie możemy go dowolnie zmieniać bez informowania o tym fakcie innych zespołów.

Heurystyki dla Integration Events

Kolejnym pytaniem jakie można byłoby sobie zadać jest to w jaki sposób wyselekcjonować Integration Events/Outside Events ze zdarzeń domenowych? Nie chcielibyśmy przecież udostępniać czegoś na zewnątrz co będzie wymagać częstych zmian. Pomóc nam w tym mogą 3 poniższe heurystyki.

Stabilność

Trzeba uważnie przysłuchiwać się temu co mówią do nas eksperci domenowi. Jeśli wyłapiemy słowo domenowe, które jest często używane w związku z nazwami dwóch aplikacji to być może jest to stabilny kandydat do zdarzenia typu Integration Event.

Wspólne zrozumienie

Co innego będzie znaczyło dla hydraulika słowo “lisek” niż dla dziecka, które zobaczyło tego zwierzaka w lesie. To samo tyczy się biznesu, w którym się obracamy. Każdy bounded context posiada swój język domenowy, jednak pomiędzy nimi jest też miejsce na uwspólnione słownictwo. Dlatego przed utworzeniem Integration Event trzeba dwa razy zastanowić się czy oznacza ono to samo dla zainteresowanych aplikacji.

Spełnienie wymagań klienta

Nie chodzi tutaj o klienta biznesowego, a o klienta naszej aplikacji np. inny mikroserwis. W przypadku, gdy zgłoszone zostanie wiele razy zapotrzebowanie na dane zdarzenie to prawdopodobnie jest to kandydat na Integration Event.

Wykorzystanie Outbox Pattern

Warto również w tym miejscu przypomnieć o wzorcu jakim jest Outbox Pattern. Z racji tego, że Integration Events nie działają w ramach tej samej transakcji bazodanowej to, aby ich nie utracić, możemy sobie pomóc wykorzystując wcześniej wymienione narzędzie do zachowania At Least Once Delivery. Nie będę się tutaj więcej rozwodził na ten temat. Po więcej zapraszam do innego wpisu z mojego bloga.

Wykorzystanie Outbox Pattern dla Integration Events
Wykorzystanie Outbox Pattern dla Integration Events

Podsumowanie

Posiadając aplikację monolityczną nie musimy przejmować się Integration Events. Jednak jeśli dobrze odrobimy lekcje na poziomie modularyzacji naszej aplikacji to w końcu będziemy mieć możliwość wyodrębniania osobnych “wysp” w postaci mikroserwisów. Wtedy już przyjdzie nam się zmierzyć z wyzwaniem jakim jest poszukiwanie tych stabilnych zdarzeń domenowych.

Mam nadzieję, że ten artykuł ułatwi Ci to zadanie. Być może dzięki niemu będziecie mieli wspólne zrozumienie w zespole pojęć jakimi są Domain Events oraz Integration Events, co ułatwi Wam pracę!