Skip to main content

What's new with NServiceBus and Azure Functions

Do you think Azure Functions are pretty great? Us too! Do you hate boilerplate code? Yeah, us too.

Have you heard of C# source generators 1 and thought they sounded pretty cool but didn’t really know how they could be useful?

In the newest version of our Azure Functions integration, we’ve used source generators to reduce the boilerplate needed to set up an NServiceBus endpoint on Azure Service Bus down to just a few lines of code.

Now, this code is all it takes to write transactionally consistent NServiceBus handlers inside of your Azure Function project.

[assembly: FunctionsStartup(typeof(Startup))]
[assembly: NServiceBusTriggerFunction("MyEndpoint", SendsAtomicWithReceive = true)]

class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder) =>
        builder.UseNServiceBus();
}

From those attributes, we’ll generate an Azure Function trigger, wire it up to an Azure Service Bus queue, and manage flowing transactions around for you. All you have to do is supply the business logic. Intrigued? Let’s dive in to see what’s new with NServiceBus and Azure Functions.

🔗Automatic trigger function generation

Both NServiceBus and Azure Functions provide abstractions over receiving and handling messages from an Azure Service Bus queue. To get them working together, we need a bit of boilerplate code to create a functions trigger that passes everything needed to NServiceBus. In the 1.0 release, that looked something like this:

using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using NServiceBus;

class FunctionEndpointTrigger
{
    readonly IFunctionEndpoint endpoint;

    public FunctionEndpointTrigger(IFunctionEndpoint endpoint)
    {
        this.endpoint = endpoint;
    }

    [FunctionName("NServiceBusFunctionEndpointTrigger-ASBTriggerQueue")]
    public async Task Run(
        [ServiceBusTrigger(queueName: "ASBTriggerQueue")]
        Message message,
        ILogger logger,
        ExecutionContext executionContext)
    {
        await endpoint.Process(message, executionContext, logger);
    }
}

We didn’t like all that boilerplate, so we picked out the important details that need to be configured, and we used source generators to allow you to create an Azure Function trigger that maps to an NServiceBus endpoint with a simple attribute.

[assembly: NServiceBusTriggerFunction("ASBTriggerQueue")]

Dropping this attribute into your Azure Functions project will automatically generate the same common code shown above at compile time. Then, you can delete your custom trigger altogether. We believe that most of the code in your project should be business logic for handling messages.

You can read more about this feature in our documentation.

🔗Consistent messaging

We also used source generators to make it really easy to use the transactional processing in Azure Service Bus that gives you consistency between your incoming and outgoing messages.

Imagine a message handler that looks like this:

class ProcessOrderMessageHandler : IHandleMessages<ProcessOrder>
{
  public async Task Handle(ProcessOrder message, MessageHandlerContext context)
  {
    await context.Send(new BillOrder { OrderId = message.OrderId });
    await context.Send(new CreateShippingLabel { OrderId = message.OrderId });
    await context.Publish(new OrderAccepted { OrderId = message.OrderId });
  }
}

When everything is working, this handler is fine. A ProcessOrder message comes in, and three messages are produced: a BillOrder command, a CreateShippingLabel command, and an OrderAccepted event.

But what happens if one of those messages fails to be sent? What if BillOrder and CreateShippingLabel are sent, but something goes wrong, and the OrderAccepted event cannot be published? This can be caused by anything from a missing event topic to a momentary network glitch.

If left unchecked, this situation will result in duplicate BillOrder and CreateShippingLabel messages being sent each time the ProcessOrder handler is retried.

We definitely do not want to bill the customer multiple times nor ship them multiple orders. What we want is for the entire operation to succeed or fail atomically. Either all three outgoing messages are produced, or none of them are. If they are produced, then the incoming message should be marked as complete.

Getting this right is not always easy. You need to make sure the incoming message and all of the outgoing messages use the same transaction, and you need to ensure that the message will not be auto-completed by the incoming Service Bus Trigger. Getting it wrong can lead to some subtle bugs that are difficult to detect in a production environment.

So, we made it easy to get it right with just one line of code. If you want to enable transactional consistency between incoming and outgoing messages in your Azure Function NServiceBus endpoint, just tell us, and we’ll take care of the rest:

[assembly: NServiceBusTriggerFunction("MyEndpoint", SendsAtomicWithReceive = true)]

This enables the sends atomic with receive transport transaction mode for ServiceBus and integrates it correctly with the Azure Functions host. You can read more about this feature in our documentation.

🔗IConfiguration

By embracing the Microsoft IConfguration API, we’ve made the setup of your Azure Functions endpoint even simpler.

If you have used any of the new hosting models from Microsoft, you probably are familiar with the new IConfiguration API. This interface allows you to load configuration from files, environment variables, and other sources. This interface is available in Azure Functions as well, as shown here:

public class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        FunctionsHostBuilderContext context = builder.GetContext();

        var jsonConfig = Path.Combine(context.ApplicationRootPath, "appsettings.json");

        builder.ConfigurationBuilder
            .AddJsonFile(jsonConfig, optional: true, reloadOnChange: false)
            .AddEnvironmentVariables();
    }
}

The new release of NServiceBus Azure Functions embraces this configuration interface, so you can use that to configure your endpoints. This is a lot simpler:

public override void Configure(IFunctionsHostBuilder builder)
{
  var configuration = builder.GetContext().Configuration;
  builder.UseNServiceBus(() => 
    new ServiceBusTriggeredEndpointConfiguration("NServiceBusFunctionEndpoint", configuration));
}

This will automatically look up configuration settings such as connections strings and license info directly from the configured sources. In fact, if you add a configuration variable called ENDPOINT_NAME, you can shrink your endpoint configuration to just this:

public override void Configure(IFunctionsHostBuilder builder) =>
    builder.UseNServiceBus();

Of course, if you don’t want to use IConfiguration to configure the endpoint or would rather do things the old way, the manual configuration overloads still exist:

public override void Configure(IFunctionsHostBuilder builder)
{
    var endpointConfig = new ServiceBusTriggeredEndpointConfiguration("NServiceBusFunctionEndpoint");
    var transport = endpointConfig.Transport;
    transport.ConnectionString("MyConnectionString");

    builder.UseNServiceBus(cfg => endpointConfig);
}

🔗Summary

You hate boilerplate, and so do we. So with the power of C# source generators in our newest version of Azure Functions, you can shrink your endpoint configuration from a couple dozen lines of code (or more!) to a couple assembly-level attributes and a tiny Startup class containing the bare essentials:

[assembly: FunctionsStartup(typeof(Startup))]
[assembly: NServiceBusTriggerFunction("MyEndpoint", SendsAtomicWithReceive = true)]

class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder) =>
        builder.UseNServiceBus();
}

If only we could do that for all your code.

To check out NServiceBus on Azure Functions, check out our sample, Using NServiceBus in Azure Functions with Service Bus triggers.

Share on Twitter

About the authors

Hadi Eskandari

Hadi Eskandari is a functional developer at Particular Software who would like to help customers succeed by leveraging cloud technologies.

Mike Minutillo

Mike Minutillo has his head in the clouds where he really enjoys reducing boilerplate and keeping the code focused on business problems.

Sean Feldman

Sean Feldman likes tinkering with various Azure-related services and is in seventh heaven when cloud and distributed technologies are combined.

Tim Bussmann

Tim Bussmann is a developer at Particular Software who likes to make distributed systems easier to understand by ensuring predictable behavior.


  1. A source generator is a new type of Roslyn analyzer that runs during compilation, inspects the code you're building, and produces additional source files that are compiled together with the rest of your code. Check out the blog post introducing source generators or the Microsoft source generator docs for more info.

Don't miss a thing. Sign up today and we'll send you an email when new posts come out.
Thank you for subscribing. We'll be in touch soon.
 
We collect and use this information in accordance with our privacy policy.