Saga Implementation Patterns: Singleton

NServiceBus sagas are great tools for managing asynchronous business processes. We use them all the time for dealing with long-running transactions, integration, and even places we just want to have a little more control over a process.

Occasionally we have a process where we really only need one instance of that process running at a time. In our case, it was a process to manage periodic updates from an external system. In the past, I’ve used Quartz with NServiceBus to perform job scheduling, but for processes where I want to include a little more information about what’s been processed, I can’t extend the Quartz jobs as easily as NServiceBus saga data. NServiceBus also provides a scheduler for simple jobs but they don’t have persistent data, which for a periodic process you might want to keep.

Regardless of why you’d want only one saga entity around, with a singleton saga you run into the issue of a Start message arriving more than once. You have two options here:

  1. Create a correlation ID that is well known
  2. Force a creation of only one saga at a time

I didn’t really like the first option, since it requires whomever starts to the saga to provide some bogus correlation ID, and never ever change that ID. I don’t like things that I could potentially screw up, so I prefer the second option. First, we create our saga and saga entity:

Our saga entity has a property “HasStarted” that’s just used to track that we’ve already started. Our process in this case is a periodic timeout and we don’t want two sets of timeouts going. We leave the message/saga correlation piece empty, as we’re going to force NServiceBus to only ever create one saga:

With our custom saga finder we only ever return the one saga entity from persistent storage, or nothing. This combined with our logic for not kicking off any first-time logic in our StartSingletonSaga handler ensures we only ever do the first-time logic once.

That’s it! NServiceBus sagas are handy because of their simplicity and flexibility, and implementing something a singleton saga is just about as simple as it gets.

Clean Tests: Database Persistence