I'm not concerned with command messages so much, as the user will know immediately whether or not it was successful.
You probably should be concerned with command messages, since those are the ones that actually cause the book of record to change. Yes, the happy path is easy, but what do you expect the user to do when their command is not acknowledged?
What I'm concerned with is events. If attaching the aggregate version number to the event, can we make the operation idempotent?
Keep in mind, event sourced entites are loaded from a history, not just events flying by (ie subscriptions). You are going to be loading those entities from the book of record, so the exact history is going to be made available to you. You don't have to worry about the events changing, because they are immutable. Similarly, you don't have to worry about the history changing, because the history is append only.
In other words, your event sourced entity supports an apply(Event)
method, and you don't have to guard it at all, you just load the events in order.
Another way to same the same thing: the history of your entity is delivered as a DocumentMessage
, not as a sequence of EventMessages
.
The concerns are similar for projections that are assembled from more than one event history.
If your entity is listening/subscribing to some other entities events (ie, your entity is an event processor) then you will need to worry about idempotency. Notice that in this use case there are two different contexts for events -- the events in the history of the event processor itself (ie, the events that describe the state changes of the event processor), and the events that the processor is reacting to. Which is to say, you have apply(Event myStateChanged)
vs when(Event somethingHappenedSomewhereElse)
You don't need to worry about idempotency for the former (see above); you make sure you react once to an event by tracking that event in your own history. Your processor might be a state machine (so the events are naturally idempotent), or you can track the event ids of the events you are subscribed to, and then make sure you don't react more than once, etc.
Oddly enough, there are places where "version of the aggregate" does come up -- they are in the command handlers. Two common forms: first, a command might target a specific version of the aggregate to apply to (immediately solving your idempotent problem); second, you'll see tracking of the version number to guard against concurrent modification.