Using NServiceBus in Azure Functions with Storage Queue triggers

Component: NServiceBus.AzureFunctions.StorageQueues
NuGet Package NServiceBus.AzureFunctions.StorageQueues (0.x)
This is a Preview project
Target NServiceBus Version: 7.x

This sample shows how to host NServiceBus within an Azure Function, in this case, a function triggered by an incoming Storage Queues message. This enables hosting message handlers in Azure Functions, gaining the abstraction of message handlers implemented using IHandleMessages<T> and also taking advantage of NServiceBus's extensible message processing pipeline.

When hosting NServiceBus within Azure Functions, each Function (as identified by the [FunctionName] attribute) hosts an NServiceBus endpoint that is capable of processing multiple different message types.

The Azure Functions SDK enforces certain constraints that are also applied to NServiceBus endpoints. Review these constraints before running the sample.

Prerequisites

Unlike a traditional NServiceBus endpoint, an endpoint hosted in Azure Functions cannot create its own input queue. In this sample, that queue name is ASQTriggerQueue.

To create the queue with the Azure CLI, execute the following Azure CLI command:

 az storage queue create --name ASQTriggerQueue --connection-string "<storage-account-connection-string>"

To use the sample, a valid storage account connection string must be configured in 2 locations:

  • AzureFunctions.Sender/local.settings.json
  • AzureFunctions.ASQTrigger/local.settings.json

Running the sample

Running the sample should launch two console windows:

  • AzureFunctions.Sender is a console application that will send a TriggerMessage to the ASQTriggerQueue queue, which is monitored by the Azure Function.
  • The Azure Functions runtime window will receive messages from the ASQTriggerQueue queue and process them using the Azure Functions runtime.

To try the Azure Function:

  1. From the AzureFunctions.Sender window, press Enter to send a TriggerMessage to the trigger queue.
  2. The Azure Function will receive the TriggerMessage and process it with NServiceBus.
  3. The NServiceBus message handler for TriggerMessage sends a FollowUpMessage.
  4. The Azure Function will receive the FollowUpMessage and process it with NServiceBus.

Code walk-through

The static NServiceBus endpoint must be configured using details that come from the Azure Functions ExecutionContext. Since that is not available until a message is handled by the function, the NServiceBus endpoint instance is deferred until the first message is processed, using a lambda expression like this:

private static readonly FunctionEndpoint endpoint = new FunctionEndpoint(executionContext =>
{
    // endpoint name, logger, and connection strings are automatically derived from FunctionName and QueueTrigger attributes
    var configuration = StorageQueueTriggeredEndpointConfiguration.FromAttributes();

    configuration.UseSerialization<NewtonsoftSerializer>();

    // optional: log startup diagnostics using Functions provided logger
    configuration.LogDiagnostics();

    // Disable persistence requirement
    configuration.Transport.DisablePublishing();

    return configuration;
});

The same class defines the Azure Function which makes up the hosting for the NServiceBus endpoint. The Function hands off processing of the message to NServiceBus:

[FunctionName(EndpointName)]
public static async Task QueueTrigger(
    [QueueTrigger(EndpointName)]
    CloudQueueMessage message,
    ILogger logger,
    ExecutionContext context)
{
    await endpoint.Process(message, context, logger);
}

Meanwhile, the message handlers for TriggerMessage and FollowUpMessage, also hosted within the Azure Functions project, are normal NServiceBus message handlers, which are also capable of sending messages themselves.

public class TriggerMessageHandler : IHandleMessages<TriggerMessage>
{
    private static readonly ILog Log = LogManager.GetLogger<TriggerMessageHandler>();

    public Task Handle(TriggerMessage message, IMessageHandlerContext context)
    {
        Log.Warn($"Handling {nameof(TriggerMessage)} in {nameof(TriggerMessageHandler)}");
        return context.SendLocal(new FollowupMessage());
    }
}
public class FollowupMessageHandler : IHandleMessages<FollowupMessage>
{
    private static readonly ILog Log = LogManager.GetLogger<FollowupMessageHandler>();

    public Task Handle(FollowupMessage message, IMessageHandlerContext context)
    {
        Log.Warn($"Handling {nameof(FollowupMessage)} in {nameof(FollowupMessageHandler)}.");
        return Task.CompletedTask;
    }
}

Related Articles


Last modified