Skip to content

Implementing Undo functionality

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.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


You’re writing an email and click “Send”. Then immediately think, “oh no!” because you sent it to the wrong person. Or maybe you’re checking out of an e-commerce website, click “Place Order” then a couple of minutes go by, and you think, “I shouldn’t have ordered that,” and have buyer’s remorse. You want to cancel or undo the action you performed in both situations. Now put your developer hat on. How would you implement undo functionality in these types of cases?

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Undo

A good example of undo functionality is canceling the sending of an email. If you’re familiar with Gmail, it has a feature to undo a recently sent email.

Once you send your message, you have the option to undo your sent message.

To illustrate this in our implementation, I also realized that Twitter could not undo sending a tweet. So I’m going to re-create a simple example of that.

I’ve created a Blazor server app allowing you to enter text and click send now. In the example below, I sent a message “this is a test message”.

On our backend, we are generating a Tweet message (command) that is being sent from our API to a message broker.

We have a consumer that asynchronously handles the Tweet command and persists that to our database. It then also publishes a Tweeted event back to our broker.

Then there is a consumer for the Tweeted event that handles that event from our message broker and, using SignalR, pushes that message back to the client.

Here’s what the code looks like when processing the Tweet command.

So that’s how it works currently. How do we implement an Undo? So that our “Tweet” can be canceled for a window of time after we send it.

Implementation

All the code for this demo is available to Developer-level members of my Patreon or YouTube channel.

To implement undo functionality, we can use delayed delivery from the Tweet command we initially sent to our message broker. This prevents the command from being consumed until the delay period has passed. It acts as a timeout. Once the timeout has passed, then a consumer will process our Tweet command.

During that timeout period, our API can expose another command to cancel the original Tweet command.

Delayed delivery has a bunch of different use cases. Check out my post Avoiding Batch Jobs by a message in the FUTURE for more examples.

We can now implement a TweetWithDelay command and a new UndoTweet command. And if a Tweet is undone, we will publish a TweetUndone event.

The above code uses an NServiceBus saga. When a TweetWithDelay is handled, it sets the Saga State to capture the incoming text/message of the Tweet. Delayed Delivery occurs when it calls RequesTimeout for 10 seconds. Once 10 seconds has passed, the Timeout method will be invoked, and we’ll create a regular Tweet command and send it. That will invoke the code above that we already had. If however the UndoTweet command is consumed, it will complete the Saga and publish our TweetUndone event. When a saga is completed, the Timeout won’t be executed.

Notice we didn’t have to change any of the existing code. This is all brand-new functionality.

In our front-end Blazor, we now can have a “Send with Delay” button that will send our new command to the broker.

What this looks like visually, I will send a tweet with “send now,” and then I will send another with “send with delay”.

When that occurs, we can show a cancel/X on the far right to allow the user to send the UndoTweet command. If you don’t within 10 seconds, as mentioned earlier, the timeout will occur, and the SendTweet will be sent, and we will remove the cancel/X on the far right.

Now if I send another tweet with a delay

However, with this tweet, I did click the undo button, which sent the UndoTweet command, which happened before the timeout occurred, so it completed our Saga, and the tweet was never processed, and we removed it from the UI via SignalR.

Client Side

You may wonder why not implement undo functionality exclusively on the front end? Why not just implement the timeout in the front end and make the API call after the timeout?

You could, but the downside is that no request would ever be sent if you close the browser before the timeout occurs. In the example of Gmail, do you want to click “Send” and then wait 30 seconds to close your browser/tab? Not really.

Join!

Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


Leave a Reply

Your email address will not be published. Required fields are marked *