Using NServiceBus in Azure Functions with Storage Queue triggers

Component: Azure Functions (Storage Queue Trigger)
NuGet Package NServiceBus.AzureFunctions.StorageQueues (0.1-pre)
This is an experimental project
Target NServiceBus Version: 7.x
This page targets a pre-release version and is subject to change prior to the final release.

The packages related to hosting NServiceBus in Azure Functions are experimental and not intended for production use:

  • NServiceBus.Serverless
  • NServiceBus.AzureFunctions.ServiceBus
  • NServiceBus.AzureFunctions.StorageQueues

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 FunctionEndpoint endpoint = new FunctionEndpoint(executionContext =>
{
    var configuration = new StorageQueueTriggeredEndpointConfiguration(EndpointName);

    configuration.UseSerialization<NewtonsoftSerializer>();

    // optional: log startup diagnostics using Functions provided logger
    configuration.AdvancedConfiguration.CustomDiagnosticsWriter(diagnostics =>
    {
        executionContext.Logger.LogInformation(diagnostics);
        return Task.CompletedTask;
    });

    return configuration;
});

Alternatively, the endpoint can be automatically configured with the endpoint name, the transport connection string, and the logger passed into the function using a static factory method provided by StorageQueueTriggeredEndpointConfiguration.FromAttributes method.

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

    configuration.UseSerialization<NewtonsoftSerializer>();

    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;
    }
}

Samples


Last modified