(This is part 4 of a long-running series on idempotence. You can go to the beginning.)
Yesterday we went through how to make multiple command messages for the same intent appear as a single command message to the account-component
.
We left off with this exercise for the reader: now that we have this de-duplicated command in a reservation stream, how would account-component
handle it in an idempotent manner?
A state-based check probably wouldn’t work because again, we’d have to keep that running list of all deposit IDs that the account has processed.
When we talked through sequential idempotence, we said that it makes it so we don’t handle the same message more than once. Through use of the Reservation Pattern, we’ve effectively turned an unbounded potential number of duplicate messages into the same message Represented visually:
To account-component
, there is only the command in the reservation stream*.
Therefore, it can just use sequential idempotence at this point. When it processes the Deposit
in the reservation stream, it will save that global_position
of 3
as the sequence
property on the resulting Deposited
event. When it recycles (handles a second, third, etc time) that Deposit
command and projects the account’s state, it’ll see the current sequence
of 3
, compare it to the incoming global_position
of 3, and because the account’s sequence
isn’t strictly less than 3
, know that it has processed this message before. We won’t deposit $10 when the user only deposited $5, even if our banking front-end sends us the command more than once.
(*Strictly speaking, the code that copies the commands from account:command
streams to accountTransaction
streams is part of the account-component
. It’s a different consumer within the component though. The consumer that handles Deposit
s and Withdraw
als never perceives the potentially duplicated commands.)