Scheduled Message Delivery and Execution


Note! Why didn't we just use Hangfire? We looked at it, but thought that our current solution avoided having to have more dependencies and the database mechanism was actually easier for diagnostic views of the scheduled messages. We do recommend Hangfire if you really just want scheduled job execution.

Schedule Message Delivery

You can send messages with Jasper, but request that the actual message sending to happen at some later time with IMessageContext.ScheduleSend():


public async Task SendScheduledMessage(IMessageContext bus, Guid invoiceId)
{
    var message = new ValidateInvoiceIsNotLate
    {
        InvoiceId = invoiceId
    };

    // Schedule the message to be processed in a certain amount
    // of time
    await bus.ScheduleSend(message, 30.Days());

    // Schedule the message to be processed at a certain time
    await bus.ScheduleSend(message, DateTime.UtcNow.AddDays(30));
}

This functionality is useful for long lived workflows where there are time limits for any part of the process. Internally, this feature is used for the retry later function in error handling and retries.

Here's a couple things to know about this functionality:

  • Whenever possible, Jasper tries to use any kind of scheduled delivery functionality native to the underlying transport. The Azure Service Bus transport uses native scheduled delivery for example
  • If the Jasper application has some kind of configured message persistence, the scheduled messages are persisted and durable even if the application is shut down and restarted.
  • The durable message scheduling can be used with multiple running nodes of the same application
  • If there is no message persistence, the scheduling uses an in memory model. This model was really just meant for message retries in lightweight scenarios and probably shouldn't be used in high volume systems

Schedule Execution Locally

This same functionality is used for the ICommandBus.Schedule() functionality as shown below:


public async Task ScheduleLocally(IMessageContext bus, Guid invoiceId)
{
    var message = new ValidateInvoiceIsNotLate
    {
        InvoiceId = invoiceId
    };

    // Schedule the message to be processed in a certain amount
    // of time
    await bus.Schedule(message, 30.Days());

    // Schedule the message to be processed at a certain time
    await bus.Schedule(message, DateTime.UtcNow.AddDays(30));
}

In the case above though, the message is executed locally at the designated time.

The locally scheduled messages are handled in the local "durable" queue. You can fine tune the parallelization of this local worker queue through this syntax:


public class ConfigureDurableLocalQueueApp : JasperOptions
{
    public ConfigureDurableLocalQueueApp()
    {
        Endpoints.DurableScheduledMessagesLocalQueue

            // Allow no more than 3 scheduled messages
            // to execute at one time
            .MaximumThreads(3);
    }
}

Schedule Execution From Cascading Messages

To schedule a message to another system as a cascading message from a message handler, you can return the ScheduledResponse object like this:


public class ScheduledResponseHandler
{
    public Envelope Consume(DirectionRequest request)
    {
        return new Envelope(new GoWest()).ScheduleDelayed(TimeSpan.FromMinutes(5));
    }

    public Envelope Consume(MyMessage message)
    {
        // Process GoEast at 8 PM local time
        return new Envelope(new GoEast()).ScheduleAt(DateTime.Today.AddHours(20));
    }
}

RequeueLater

The RetryLater() mechanism in message error handling is using the scheduled execution. See Error Handling for more information.