Using NServiceBus in Azure Functions with Service Bus triggers

Component: Azure Functions (Service Bus Trigger)
NuGet Package NServiceBus.AzureFunctions.ServiceBus (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 Service Bus 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 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 ASBTriggerQueue.

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

az servicebus queue create --name ASBTriggerQueue --namespace-name <asb-namespace-to-use> --resource-group <resource-group-containing-namespace>

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

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

Running the sample

Running the sample will launch two console windows:

  • AzureFunctions.Sender is a console application that will send a TriggerMessage to the ASBTriggerQueue queue, which is monitored by the Azure Function.
  • The Azure Functions runtime window will receive messages from the ASBTriggerQueue 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 =>
{
    var configuration = new ServiceBusTriggeredEndpointConfiguration(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 ServiceBusTriggeredEndpointConfiguration.FromAttributes method.

private static readonly FunctionEndpoint autoConfiguredEndpoint = new FunctionEndpoint(executionContext =>
{
    // endpoint name, logger, and connection strings are automatically derived from FunctionName and ServiceBusTrigger attributes
    var configuration = ServiceBusTriggeredEndpointConfiguration.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 Run(
    [ServiceBusTrigger(queueName: EndpointName)]
    Message message,
    ILogger logger,
    ExecutionContext executionContext)
{
    await endpoint.Process(message, executionContext, 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