How to not process the same message more than once

(This is part 2 of a long-running series on idempotence. You can go to the beginning.)

Yesterday we examined state-based idempotence, which protects against processing a particular domain operation more than once, even if you get additional messages telling you to do so.

Sometimes state-based idempotence doesn’t work. For example, again referencing the Eventide account-component example, how would an account know if it has processed a given Deposit request? The amount? No, you can deposit $5 twice into the same account. The time of the depoist would rely heavily on clock synchronization and low transaction volume.

We could give each deposit a unique identifier that the account could track, but imagine a long-running account with millions of transactions. Scanning that list each time to see if we’ve processed a deposit would be time and memory intense.

Sequential idempotence leverages a message’s global position

Every message in Message DB has a global_position property, or its position in the sequence of messages throughout the Message Store. Commands are messages and precede any events they cause. It follows then that any event produced by a command would have a global_position strictly greater than the command that caused it.

When we write an event from a command, we can store the command’s global_position on the event as a sequence property (see an example in account-component).

When we project the account, we can copy any sequence found in an event to the account.

So then, given a command, we could ask the account component, “have you already processed through this command’s global_position?”, which is exactly what account-component does with these lines in the same handler linked above:

# ...
sequence = withdraw.metadata.global_position

if account.processed?(sequence)
  # ...
  return
end

And boom. This handler will never process that same message more than once.

Caveats

This doesn’t mean we should always use this mechanical idempotence method. First of all, it depends entirely upon message global_positions, which in turn depend on which Message Store contains those messages. Sometimes, for operational reasons, we move categories of messages to different Message Stores. All of your sequences would be shot to heck after such a move.

Another consideration, while sequential idempotence protects against processing the same message more than once, what happens if you have two separate messages that are trying to do the same thing? What if your clients send two separate messages for deposit 123 because, wait for it, networks fail, and they didn’t know if the message was received?

In this case, we need a way to recognize that all these messages have the same intent. That’s what The Reservation Pattern solves, and it’s what we’ll cover tomorrow.


Like this message? I send out a short email each day to help software development leaders build organizations the deliver value. Join us!


Get the book!

Ready to learn how to build an autonomous, event-sourced microservices-based system? Practical Microservices is the hands-on guidance you've been looking for.

Roll up your sleeves and get ready to build Video Tutorials, the next-gen web-based learning platform. You'll build it as a collection of loosely-coupled autonomous services, developing a message store interface along the way.

When you're done, you'll be ready to contribute to microservices-based projects.

In ebook or in print.