After reading/watching a lot about Event Sourcing, there is one thing I don't fully understand: Events that lead to triggering of other events.
Let's take StackExchange's "close question" feature as an example. The feature requires
- commands like
VoteToCloseQuestion
, andCloseQuestion
(direct close for moderators), and - events like
VotedToCloseQuestion
andClosedQuestion
-- note the use of past participle to express "historical facts".
The crucial point: If we reach a state of having 5 VotedToCloseQuestion
, where does the event ClosedQuestion
come from exactly?
Often the architecture of Event Sourcing + CQRS is presented in diagrams like:
(from a talk by Dennis Doomen)
(from a talk by Mathew McLoughlin)
What surprises my in these diagrams. The command handler has no knowledge of past events apparently. Because of that, I fail to see how events triggering other events can work exactly.
So far, the best hint I have found was this Q/A. The top-voted answer mentions the notion of "Event Handlers" that are responsible for feeding commands back into the system as a reaction to events. In an attempt to make this more concrete, I came up with the following interpretation (focusing on the command side only, pipes are topics/queues, solid arrows is publish, dashed arrows is subscribe):
However, such an architecture has some weird implications:
- The CommandHandler is only subscribed to the commands topic. In particular, a CommandHandler itself is not subscribed to the event stream itself. This means that the CommandHandler cannot know the full state of the system. As a result, the only decisions it can make are stateless transformations of commands to events -- which is surprisingly boring.
- The EventHandler on the other side listens to the stream of events, so it can compute the entire application state (which it may store in materialized view local to the EventHandler service). This is the place where the business logic can live, and where we can make decisions to trigger follow-up commands.
Looking again at the introductory example, the sequence of commands/events would become:
- Command:
VoteToCloseQuestion
- CommandHandler issues event:
VotedToCloseQuestion
- EventHandler increments internal vote counter, but does nothing because < 5
- ... 3 more ...
- Command:
VoteToCloseQuestion
- CommandHandler issues event:
VotedToCloseQuestion
- EventHandler increments internal vote counter, sees the count is 5, and issues the
CloseQuestion
command - Command:
CloseQuestion
- CommandHandler issues event:
ClosedQuestion
It seems to work, but I'm not sure if I'm on the right track. In particular I'm wondering:
- Does the separation into commands and events really add value like this? It feels like I have just duplicated every command as an event again, and since the CommandHandler cannot contain significant business logic due to lack of context, it becomes mostly boilerplate?
- Others (e.g. the CQRS FAQ) seem to imply that the business logic is in the command handler. How is this possible if it cannot know the application state?
The CommandHandler cannot know the full state of the system
-- Correct. That's what the purpose of the Event Store is. The Event Store allows you to "play back" the events to get the history of the state changes.It feels like I have just duplicated every command as an event again
-- If it helps, think of a command as a "patch panel" of sorts. A command can be patched into a bit of code that generates an event, but it can also be patched into some other piece of code that, say, disables a button.ConfirmOrder
(command) vsOrderConfirmed
(event) from the CQRS FAQ stating: Unlike an event, a command is not a statement of fact; it's only a request, and thus may be refused. So if the CommandHandler lacks the context to refuse an event, it can only issue anConfirmOrderReceived
event or so. Later the EventHandler has the necessary context to translate that into a new commandReallyConfirmOrder
, and finally the CommandHandler can publish theOrderConfirmed
event? The job of the CommandHandler feels redundant somehow.