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.
A long-running business process could last seconds to days, you cannot lock resources within a service using a distributed transaction. So what’s the alternative? The real world has a solution, it’s a reservation. The reservation pattern allows you to have a time-bound limited guarantee which allows you to coordinate with other services.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
When working with a traditional monolith, you can use a database transaction. Wrap all the relevant database calls within a transaction, and if anything goes wrong executing all the steps in a business process, you simply rollback.
When working with a system that has many distributed services, this isn’t an option, unless you have a distributed transaction. Which you likely won’t. So what’s the alternative? One solution is to use the reservation pattern that allows you to create a time-bound lock on a single resource that allows you to execute your long-running business process and have a limited guarantee the resource will be available when required.
A common example of this is requiring a unique constraint on a username or email address. If you have multiple services that are a part of a user registration process, not all of them will be consistent in that constraint. Likely one service can own that constraint, however, if another user is doing the sign-up concurrently, we could have duplicates in other services.
To better illustrate this, let’s turn to the real world where we use the reservation pattern in many different scenarios.
I recently made an online order for a product at a big box store. This online order was for pickup, not delivery. When I placed the order, the website said there was only one item available at my local store.
Once I placed my order, this is the email I received:
Notice at the very top, they state that I’ll receive another email once the item that I purchased is ready for pickup.
This is because once the order is sent to my local store, an employee has to physically go get the item off the shelf so nobody else buys it. Because there was only one item remaining (or so their website said), it may be also possible that information is stale and there is no item available.
The employee going to get the item off the shelf is reserving the item for me.
Once they found the item on the shelf, they brought it to a designated area in the store for pick-up orders. They also marked my order as being ready for pickup, which triggered this email
One important aspect of a reservation is it’s time-limited. You’ll notice in the email states that if I don’t pick up the order within 7 days, they will refund my credit card and put the item back on the shelf.
They are providing a lock/hold on my item and that lock will expire in 7 days.
When I go to the local store and pick up my order, this is confirming my reservation.
We can implement the reservation pattern in code to solve similar types of problems. In the example of user registration, we can create a reservation on a username at the beginning of the registration process. Once the user completes the user registration process, we can confirm the reservation.
If the user never completes the registration process, the username will expire after the time we define, letting someone else try and register the same username.
The reservation pattern has 3 important aspects. Reserving, Confirming, Expiring.
To first illustrate this in code, I’m going to show the synchronous version first, followed by the asynchronous version, which is generally more applicable.
The user registration first checks the reservation of the username exists. If it doesn’t it will create a new account, save it to our database, then tell the reservation it’s complete.
The reservation itself has the ability to Reserve, Complete. Internally after 5 seconds, it will Expire the username if it’s still reserved. This is a sample, not using a real database nor handling concurrency, as you likely would in a real production environment.
Now to illustrate the asynchronous version, I’m going to be using NServiceBus Saga to handle all the interactions and coordinate the reservation. This is closer to the real world in an application where there are likely many different steps involved in a long-running business process.
The process is kicked off when the UserRegistrationStarted event is consumed. From there it will send a ReserveUsername command, which will handle the actual reserving the username.
There are two things to point out. First is that we are sending an ExpireReservation command that will get delivered in 10 seconds. This is the expiry to remove the username from the reservation if it’s still there. The second is we now publish a UsernameReserved event which is also handled UserRegistration. The UserRegistration will then send a CreateUserAccount command. When that command is executed (code not shown) it will publish a UserAccountCreated event, which is again handled by UserRegistration, which can then send a ConfirmUsernameReservation to complete the reservation process.
Developer-level members of my YouTube channel or Patreon get access to the full source for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.
The reservation pattern is used in the real world all the time. We can leverage the pattern in code to provide time-bound limited guarantees between services for a long-running business process. It’s a limited lock that can expire.
Like my real-world example, I placed an online order for pickup. The item was reserved for me which prevented me from wasting my time by going to the store and the item not being available. When the employee took the item off the shelve, that reservation had 7 days for me to pick up the item, or that reservation would expire and someone else could purchase it.
Sometimes you just need to look to the real world for a solution.