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.
I’ve run into situations where I need to perform actions requested by the client that may take a bit of time. The length of time could be variable and I don’t want to have the client waiting for a response. Let’s take a look at using the Mediator Pattern with Hangfire as a solution to this problem. In a Web API scenario, the request may come in as a PUT or POST from the client. The API would validate the request, send the task to a queue and then return the client a 202 HTTP status code.The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place. There is no facility for re-sending a status code from an asynchronous operation such as this.This way we can handle the work in another process or thread from the initial request.
Hangfire & MediatR
I’ve posted about MediatR before, a nice library that implements the Mediator Pattern for in-process messaging. I use it for both handling both Commands and Queries. For our purposes with Hangfire, it will be used for sending requests that are Commands. I’ve also previously posted about Hangfire, a really great library in .NET for executing background tasks in all types of .NET applications. If you are interested in Hangfire, take a look at this post first. Combining these two libraries allows us to take MediatR out of process and use the same implementation we are used to using in-process.Helper
Hangfire provides the ability for your task to be a method on a class. However, the above will not work as there is an issue with serializing the Command. The issue appears to be with MediatR’s Send<Unit> method. Trying to serialize the expression when it contains a type parameter may be the issue. If you are aware of what this specific issue is, please let know in the comments. Since we are using commands that have no return type I just created a static helper function.SetSerializerSettings
Also, in order for serialization to be correct we must set the Json.NET TypeNameHandling setting.Setting to include type information when serializing JSON and read type information so that the create types are created when deserializing JSON.
Example
So let’s put this simple example all together. I’m putting the Hangfire server where I’m also creating the task (client). Because of this, I’m printing out which thread is sending the task, as well as which thread the task is executed on. If you were running multiple Hangfire server instances, this would happen on any one of them and allows you to distribute tasks. The resulting output displays that the task was sent from Thread 8 and was executed on Thread 18.Comments
Let me know if you are using MediatR or the Mediator Pattern with Hangfire and how you have implemented it. Please share in the comments below or on twitter.Related Posts
Here are some other posts that relate to Hangfire or MediatR that I have written.
Pingback: Background Tasks in .NET - CodeOpinion
It feels like there got some feature overlap between Hangfire and MediatR since both of them are message oriented. So why bothered using them altogether? Hangfire would be enough to do the job.
Well, this post is about using Mediator Pattern to decouple the dependencies between controllers and the services. To answer the above question by myself…
Yes, since MediatR is about in-process messaging it just provides a common interface for executing commands. Hangfire provides the infrastructure of persisting and distributing the invocation of commands out of process.
really good use case thanks!
I don’t know if this is of any help, it’s hard to see without running the code. But I’ve quickly built up some good experience with Hangfire, and I thought I’d try to help.
You put this code up first, saying there was a problem with it and serializing the command:
BackgroundJob.Enqueue(mediator => mediator.Send(new Command()));
I think I see two potential problems with this ^^^
(1) The first is “new Command()”. I was confused why you’d have to touch your JSON.Net serialization settings at all, as in all of my Hangfire use I’ve never had to touch them. But I know why, and it’s because I’ve followed the Hangfire “best practice” with regards to queuing jobs (http://docs.hangfire.io/en/latest/best-practices.html#make-job-arguments-small-and-simple).
I think it’s a better practice to use only base types in the expression you provide to Hangfire. All of the data that you’d normally put on a Command is better stored in the database, providing you with some sort of int or guid ID that you can pass in your expression this way:
var command = commands.Create(new Command());
var id = command.Id;
BackgroundJob.Enqueue(mediator => mediator.Send(id));
Then make a “Send” method that takes your “id”, looks up the command in the database, and then deserializes the command and fires the original method.
Look, Hangfire is great, and sidekiq is great, and I love them all. But you can’t marry these background processors, and you shouldn’t trust them to that extent. A command is incredibly important, so important that you don’t want to toss it on a queue to be deleted a day after the job is complete (the Hangfire default). Store that essential data in the database, and simplify the expression that you’re providing to Hangfire. Make it’s job easier, and it’ll work better for you.
(2) The second issue is the BackgroundJob.Enqueue(mediator => …); call.
I didn’t even know there was a generic form of BackgroundJob.Enqueue, as I’ve never needed it. I’ve only used it like so:
BackgroundJob.Enqueue(() => mediator.Send(id));
Hangfire serializes the expression… and uses that expression to figure out how to execute the command elsewhere. When it sees “mediator” it will remember the type, but not the exact instance. When the job processor picks the job up, it will see “mediator” and attempt to construct a new Mediator instance from the IoC container. This is a really great idea on Hangfire’s part (so long as devs remember not to keep state in the “mediator” instance).
But this should negate the need for any sort of static “helper” class with a singleton property that has to be set. I’m a little confused in your example with that helper, and I’m not sure it’s providing an accurate representation of how this process works since your console app that queues the Hangfire job is also the application that serves as the background processor. Setting the static helper works here because it’s all in the same Main(), but would it work that way if these were two different applications? I’m not sure… but even if they did, I’d still avoid the static. Better to rely on Hangfire’s expression handling and the IoC container.
The **worst** that I’ve had to do with implementing Hangfire in my application is to create methods that take in .Net base types… like an overloaded method that takes “id” instead of the full object. But I think these practices are a good thing after all, as they’re simpler and safer when you start dealing with distributed computing like this.
Yes, the reason Json.NET config is so that it puts the type information into the serialization, otherwise it won’t deserialize. Indeed primitives are the answer. Having said that, I’m not in love with the idea that I’m serializing the command and putting that in the database. I see on Hangfire website as a best practice, I just can’t behind that as the solution yet. Something doesn’t seem right with storing command information in two places.
You’re not storing it in two places, though. Hangfire isn’t storage, it’s background processing. And it’s just one way to do that, out of many. But all of them are the same that it’s a different instance of your application. You’re sending a message to another application to do something.
So just keep it at that level: How do you send messages to other apps? Do you send complex, jsonified maps to static objects? Do you throw the message to the other computer with no receipt or confirmation that the job was done? Do you require all instances of your application be carefully in sync, so much so that one message may be invalid on the other side? Do you risk the chance that jobs may get processed repeatedly due to errors or mistakes?
The Hangfire best practices aren’t just for Hangfire – they apply to all background processing. I’ve used sidekiq much more than Hangfire, with an integration to the web app that you’ll never get in a .Net MVC app, and I had to follow the same best practices.
There is one more thing I meant to bring up. I’ve run Hangfire with one instance, yet seen concurrent behaviors. I say this because I log what I throw to it, and the timestamps show multiple workers at once. So I believe that it’s capable of initiating multiple threads at once.
… which makes me think that this example might work better if you broke out the sender from the receiver. Demonstrating that the job if fired in a different thread is one thing, but it’s still working with the same static instance of the mediator you set up. If you added separate Hangfire instances, this obviously wouldn’t be the case. Your static mediator would/should be fulfilled from the IOC container, which brings the entire static into question: Why bother using a static mediator at all?
If you want to change the Json settings to better assist sending more complicated objects through Hangfire, that’s cool. But otherwise I’d recommend leaning on the IOC container and stick with the more common Hangfire setup.
I’ll explore more but the issue with:
BackgroundJob.Enqueue(mediator => mediator.Send(new Command()));
is because type parameter of Send. The JobActivator then proves to be useless because the type passed to ActivateJob is null, so you can’t use your existing container to then resolve mediator.
Again, I’ll explore more and create a working example with the producer and consumer in different processes/applications.
We use WebJobs instead, the SDK that persists messages in simple Azure Queue, allows relatively rich configuration and customization. Also helps to decouple the web/api app from the background processing. The only thing one needs to return 202 Accepted once Azure Queue client returned 200 OK.