Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
This post serves as a guide for how you can use a Message identification (Message-IDs) on your messages (events and commands) to handle concurrency.
This post is in a series related to messaging. The overview is available in my Message Properties post.
YouTube
If you haven’t already check out my YouTube channel.
Message-IDs
Each message, regardless of it being an event or a command, should contain a way to identify its specific instance of that message. This is as simple as adding a GUID/UUID to your messages:
No other message (event/command) should ever use this ID (unless you’re also using a message owner). It’s a one time only usage that should be unique.
The producer of the event should be creating this ID before it publishes it. It shouldn’t be hydrated by some middle party.
Concurrency
Most systems support at least once messaging. Meaning, they will deliver a message to the consumer at least once. This means it can be delivered more than once.
Message handlers should be reentrant. You should be able to invoke multiple instances of a message handler concurrently and safely.
Meaning if the exact same instance of an InventoryAdjusted event was invoked twice (with the same ID), at the same time, the outcome would be the inventory on hand would increase by 10, not 20.
We can achieve this by having our message handler use the message ID apart of the same transaction it’s using apart of its state change.
Here’s an example using pseudo C#
The example above is using a unique key on Handler and MessageID columns in the Concurrency table. When we either try to insert the record or commit the transaction, it will fail if the unique constraint fails.
Events
Since an event can have multiple consumers/handlers is the reason why I’ve included the name of the handler as apart of the unique constraint. This is to demonstrate if you were using the same concurrency table for multiple handlers. In the case of event handlers, you want each handler to process the event, but not more than once each.
If you were using this for commands, you wouldn’t need to record the handler, just the MessageID.