The question assumes the use of Event Sourcing.
When rebuilding current state by replaying events, event handlers should be idempotent. For example, when a user successfully updates their username, a UsernameUpdated
event might be emitted, the event containing a newUsername
string property. When rebuilding current state, the appropriate event handler receives the UsernameUpdated
event and sets the username
property on the User
object to the newUsername
property of the UsernameUpdated
event object. In other words, the handling of the same message multiple times always yields the same result.
However, how does such an event handler work when integrating with external services? For example, if the user wants to reset their password, the User
object might emit a PasswordResetRequested
event, which is handled by a portion of code that issues a 3rd party with a command to send an SMS. Now when the application is rebuilt, we do NOT want to re-send this SMS. How is this situation best avoided?
I think you are mixing two separate concepts here. The first is reconstructing an object where the handlers are all internal methods of the entity itself. Sample code from Axon framework
}
Surely you wouldn't put code that talks to an external API inside an aggregate's method.
The second is replaying events on a bounded context which could cause the problem you are talking about and depending on your case you may need to divide your event handlers into clusters.
See Axon frameworks documentation for this point to get a better understanding of the problem and the solution they went with.
Replaying Events on a Cluster
There are two messages involved in the interaction: commands and events.
I do not regard the system messages in a messaging infrastructure the same as domain events. Command message handling should be idempotent. Event handlers typically would not need to be.
In your scenario I could tell the aggregate root 100 times to update the user name:
The above code would result in a single event so reconstituting it would not produce any duplicate processing. Even if we had 100
UserNameChanged
events the result would still be the same as theOn
method does not perform any processing. I guess the point to remember is that the command side does all the real work and the event side is used only to change the state of the object.The above isn't necessarily how I would implement the messaging but it does demonstrate the concept.
TLDR; store the SMS identifier within the event itself.
A core principle of event sourcing is "idempotency". Events are idempotent, meaning that processing them multiple times will have the same result as if they were processed once. Commands are "non-idempotent", meaning that the re-execution of a command may have a different result for each execution.
The fact that aggregates are identified by UUID (with a very low percentage of duplication) means that the client can generate the UUIDs of newly created aggregates. Process managers (a.k.a., "Sagas") coordinate actions across multiple aggregates by listening to events in order to issue commands, so in this sense, the process manager is also a "client". Cecause the process manager issues commands, it cannot be considered "idempotent".
One solution I came up with is to include the UUID of the soon-to-be-created SMS in the
PasswordResetRequested
event. This allows the process manager to only create the SMS if it does not yet already exist, hence achieving idempotency.Sample code below (C++ pseudo-code):
There are a few things to note about the above solution:
Firstly, while there is a (very) low possibility that the SMS UUIDs can conflict, it can actually happen, which could cause several issues.
Communication with the external service is prevented. For example, if user "bob" requests a password reset that generates an SMS UUID of "1234", then (perhaps 2 years later) user "frank" requests a password reset that generates the same SMS UUID of "1234", the process manager will not queue the SMS because it thinks it already exists, so frank will never see it.
Incorrect reporting in the read model. Because there is a duplicate UUID, the read side may display the SMS sent to "bob" when "frank" is viewing the list of SMSes the system sent him. If the duplicate UUIDs were generated in quick succession, it is possible that "frank" would be able to reset "bob"s password.
Secondly, moving the SMS UUID generation into the event means you must make the
User
aggregate aware of thePasswordResetProcessManager
's functionality (but not thePasswordResetManager
itself), which increases coupling. However, the coupling here is loose, in that theUser
is unaware of how to queue an SMS, only that an SMS should be queued. If theUser
class were to send the SMS itself, you could run into the situation in which theSmsQueued
event is stored while thePasswordResetRequested
event is not, meaning that the user will receive an SMS but the generated password reset code was not saved on the user, and so entering the code will not reset the password.Thirdly, if a
PasswordResetRequested
event is generated but the system crashes before thePasswordResetProcessManager
can create the SMS, then the SMS will eventually be sent, but only when thePasswordResetRequested
event is re-played (which might be a long time in the future). E.g., the "eventual" part of eventual consistency could be a long time away.The above approach works (and I can see that it should also work in more complicated scenarious, like the
OrderProcessManager
described here: https://msdn.microsoft.com/en-us/library/jj591569.aspx). However, I am very keen to hear what other people think about this approach.