Zgłębiając tematykę Domain Driven Design (DDD) zainteresowałem się różnicami pomiędzy zdarzeniami (events) a komendami (commands) oraz kiedy powinniśmy które stosować. I jeśli przyjrzymy się definicji to na pierwszy rzut oka wygląda jakby rozgraniczenie było oczywiste. Porównajmy zatem Events vs Commands.
Event
Event – wysyłamy gdy chcemy zakomunikować, że coś się wydarzyło i nie za bardzo nas interesuję kto otrzyma to powiadomienie i co z tym zrobi. Na Event może nasłuchiwać wielu subskrybentów – relacja jeden do wielu. Ważnym aspektem jest tutaj czas – event jest wysyłany po zaaplikowaniu akcji – mówi o tym co się już zdarzyło.
Zachęcam do zapozniania się z defnicją EventMessage z Enterprise Integration Patterns.
Command
Command – to bardziej opakowany request, który wysyłamy do konkretnego (teoretycznie tylko technicznego) serwisu w celu wykonania przez ten serwis danej czynności – relacja jeden do jeden. W tym przypadku jeśli chodzi o czas to jesteśmy przed wykonaniem akcji. W przypadku błędu w wysłaniu komendy – nic się jeszcze nie zdarzyło, możemy powtarzać próbę wysłania command.
Zachęcam również do zapozniania z defnicją CommandMessage z Enterprise Integration Patterns.
Wydaje się proste, prawda?
Sprawdźmy to na przykładach
Niestety z przykładami do DDD jest zawsze cieżko. Jeśli weźmiemy takie z prawdziwego systemu to ciężej będzie je analizować nie znając całej domeny. Pozostają więc oklepane ecommerce lub bank.
Kolejność akcji użytkownika:
- Order Creation
- Adding Product to the Order
- Tax Calculation for the Order
- Order Confirmation
- Order Payment
Przykłady komend (commands)
W przypadku command wszystko wykonuje się synchronicznie. User tworzy zamówienie, dodaje produkty do zamówienia, potwierdza zamówienie i je następnie opłaca. Komunikacja przechodzi przez kolejkę, na której odrazu dostaje dany komponent odpowiedź.
Przykłady zdarzeń (events):
W przypadku eventów zastosowaliśmy wszędzie komunikację asynchroniczną z osobnymi topic-ami dla responsów. Tutaj można się zastanowić czy w przypadku komunikacji wejściowej PaymentRequested – Event oraz Tax Calculation Requested – Event nie można by zastąpić przez command a jako odpowiedź dostawalibyśmy tylko eventy. Jest to właśnie taki przypadek, w którym tak na prawdę nie ma różnicy czy użyjemy eventu czy command. Do momentu, w którym nasłuchuje tylko jeden komponent!
Przykład w świecie rzeczywistym
- [Klient w barze -> kelnerka] poproszę 2 piwa [Command]
- [Kelnerka -> barman] klient zamówił dwa piwa [Event]
- [barman -> kelnerki] 2 piwa gotowe [Event]
- alternatywnie:
- [barman -> kelnerki] (podaj) 2 piwa dla tego pana [command]
- [kelnerka -> klient] 2 piwa gotowe, proszę sobie zabrać [event]
- alternatywnie:
- [kelnerka -> klient] te 2 piwa są dla pana, proszę [command]
Events vs Commands – czy może oba razem?
Patrząc na przykład powyżej, i pomijając wcześniejesze definicje (event – 1 do wielu, commend – 1 do 1) możemy założyć że eventów możemy użyć zamiast komend. Jak i na odwrót – komend zamiast eventów. Możemy również, użyć ich razem (nazwał bym to przeplatanką): komenda triggeruje akcje w serwisie następnie wysyłany jest event komunikujący zaaplikowanie akcji. Na ten wysłany event reaguje kolejny serwis, który z eventu wykonuje wewnętrzną komendę zmieniająca stan w serwisie i generowany jest kolejny event do kolejnego serwisu.
Transport Layer
Popatrzymy na warstwę komunikacji między serwisami w odniesieniu do events jaki commands. W przypadku command najczęściej użyjemy kolejki (Queue), w przypadku events powinniśmy zastosować publish/subscribe (Topic).
Jeśli chodzi o konkretnych brokerów jakich użyć to raczej większość wspiera oba podejścia, queue oraz topic:
- IBM MQ
- Apache Kafka
- RabbitMQ
- ActiveMQ
- Amazon SQS (Simple Queue Service)
- Google Cloud Pub/Sub
Kto decyduje o strukturze Zdarzenia/Komendy?
Kolejną istotną różnicą, której na pierwszy rzut oka nie widać, miedzy event a command jest odpowiedzialność za strukturę. W przypadku eventu, to publisher będzie decydował, jak ma wyglądać dany event. Jeśli z definicji nie interesuje go, czy ten event ktoś przeczyta, i jeśli tak, to ile podmiotów, to na tej samej podstawie on jest właścicielem struktury i decyduje, jak ma dany event wyglądać.
Inaczej jest w przypadku command. Tutaj istnieje bezpośrednia komunikacja między dwoma serwisami i to odbiorca decyduje co może przyjąć i jak ma wyglądać komenda, którą obsłuży.
Async/Sync w odniesieniu do Events vs Commands
Po stwierdzeniu, że komand używamy w kolejkach a event-ow w topic-ach, możemy stwierdzić kolejną różnice. Domyślnie każdy command jest synchroniczny natomiast, każdy event będzie asynchroniczny. Chociaż tak na prawdę nic nie stoi na przeszkodzie aby wysłać command w stylu asynchronicznym przez kolejkę i oczekiwać na odpowiedź zwrotną na kolejce zwrotnej w stylu asynchronicznym. Jeśli na przykład spodziewamy się, że procesowanie takiego command będzie trwało długo.
Podsumowanie
Wracając do przedstawionych wyżej przykładów, czy możemy stwierdzić, że któreś z podejść jest lepsze od drugiego? Wydaje, się, że podane przykłady są zbyt trywialne aby dostrzec potencjalne wady i zalety każdego z nich. W przypadku podejścia z eventami rozszerzalność takiego rozwiązania wydaje się łatwiejsza. Podpięcie dodatkowego serwisu nasłuchującego na event „Payment Processed” jest łatwiejsze niż w przypadku podejścia request/response gdzie aby się wpiąć w proces musimy zrobić modyfikację w istniejącym serwisie i dodatkowo dostawić nową kolejkę lub zrobić komunikację bezpośrednią. Oczywiście temat jest bardziej złożony i wychodzi poza ramy samych różnic pomiędzy Event vs Command. Ten post oryginalnie miał się skupić tylko na tych różnicach właśnie a i tak niechcący wyszedł za bardzo na architekturę Events/Messaging. Dlatego na tym zakończę.
Źródła:
Mastering Software Architecture: Part 5 — The Enigma of Event-Driven Architecture
Event Driven Architecture, The Hard Parts : Events Vs Messages