Support for Azure Functions
Update: NServiceBus hosting with Azure Functions for Azure Service Bus is now 1.0. In addition to full production support, that means API stability and backporting of bugfixes. See our release announcement here.
Microsoft Azure Functions provide a simple way to run your code in the Azure Cloud. They are easy to deploy, scale automatically, and provide many out-of-the-box ways to trigger your code. Rather than pay for an entire virtual machine, you only pay for compute while your code is being executed.
NServiceBus makes Azure Functions even better. You can use a simple but powerful API to consume and dispatch messages, add robust reliability features like delayed retries, an extensible message processing pipeline, and a suite of tools to help you monitor and maintain your message-driven system. We think they go together like milk and cookies.
Let’s take a look at how using NServiceBus and Azure Functions together can make your serverless applications even better.
🔗Better together
While you can use Azure Functions HTTP triggers to create a web API, what’s more interesting with NServiceBus is to set up a queue-triggered Function using Azure Service Bus.
Normally, you’d need to set up a function on a queue and then handle that message natively:
[FunctionName("MyFunction")]
public static async Task Run(
[ServiceBusTrigger(queueName: "queuename")]
string message,
ILogger logger,
ExecutionContext executionContext)
{
// Process the message
}
This is fine but has its limits. The message can be just about any type, but you can only support one type. If you need another message type, you need another queue for that, or you have to get fancy and handle the message serialization and routing yourself. It can get complex pretty quickly.
NServiceBus solves all that with a little bit of code that converts the function into a full-fledged NServiceBus endpoint that can handle multiple message types:
[FunctionName("MyFunction")]
public static async Task Run(
[ServiceBusTrigger(queueName: "queuename")]
Message message,
ILogger logger,
ExecutionContext executionContext)
{
await endpoint.Process(message, executionContext, logger);
}
static readonly FunctionEndpoint endpoint = new FunctionEndpoint(executionContext =>
{
var serviceBusTriggeredEndpointConfiguration = ServiceBusTriggeredEndpointConfiguration.FromAttributes();
return serviceBusTriggeredEndpointConfiguration;
});
After this, to handle any message, you add a handler, just like any other NServiceBus endpoint:
class MyRequestHandler : IHandleMessages<MyRequestClass>
{
static ILog Log = LogManager.GetLogger<MyRequestHandler>();
Task Handle(MyRequestClass message, IMessageHandlerContext context)
{
Log.LogInformation("Hey, a message with id {0} arrived!", message.Id);
}
}
Now you can take advantage of all of the powerful features of NServiceBus in your Azure Function.
🔗Evolving message contracts
Due to the way Azure Functions encodes the incoming message type in the function’s method signature, things can get dicey when you need to evolve that contract without breaking in-flight messages.
In this example, the function processes a MyRequestClass
message and emits a MyNextRequestClass
message:
[FunctionName("MyFunction")]
[return: ServiceBus("nextreceiverqueue")]
public static async Task Run(
[ServiceBusTrigger(queueName: "receiverqueue")]
MyRequestClass message,
ILogger logger)
{
logger.LogInformation("Hey, a message with id {0} arrived!", message.Id);
return new MyNextRequestClass { Id = message.Id };
}
If your message is JSON serialized, this just works. However, message contracts are not always stable. Any burgeoning project is sure to need to change a message contract eventually due to evolving business requirements, changes to third-party integrations, or just plain old growing pains.
Since you may have messages with the old contract still in-flight, it is safest to create a new Function with a new queue for the new contract version. Otherwise, you will have to handle the serialization yourself in the Function handler and add the alternate paths to process each contract version.
With NServiceBus, we can just add another handler to the project:
class MyRequestHandlers :
IHandleMessages<MyRequestClass>,
IHandleMessages<MyRequestClassV2>
{
static ILog Log = LogManager.GetLogger<MyRequestHandler>();
async Task Handle(MyRequestClass message, IMessageHandlerContext context)
{
Log.LogInformation("Hey, a v1 message with id {0} arrived!", message.Id);
await context.Send(new MyNextRequestClass { Id = message.Id });
}
async Task Handle(MyRequestClassV2 message, IMessageHandlerContext context)
{
Log.LogInformation("Hey, a v2 message with id {0} arrived!", message.Id);
await context.Send(new MyNextRequestClassV2 { Id = message.Id });
}
}
🔗Simple message dispatching
NServiceBus also makes it much easier to send messages within the Function. For example, what if we wanted to send another message at the end of the previous handler?
In a normal Azure Function, the extremely flexible bindings offer a lot of options for sending outgoing messages. An attribute must decorate the method to use the function’s return value as an outgoing message, but can also decorate method parameters instead. Those parameters could be a normal type if you want to send one message, but if you need to send multiple messages then you must use an ICollector<T>
or an IAsyncCollector<T>
. In the attribute parameters, you need to specify a different EntityType
if you want to publish to a topic rather than send directly to a queue.
[FunctionName("MyFunction")]
[return: ServiceBus("nextreceiverqueue")]
public static async Task Run(
[ServiceBusTrigger(queueName: "receiverqueue")]
MyRequestClass message,
[ServiceBus("topic", Connection = "ConnectionString", EntityType = EntityType.Topic)]IAsyncCollector<SomethingHappenedEvent> collector,
ILogger logger)
{
logger.LogInformation("Hey, a message with id {0} arrived!", message.Id);
await collector.AddAsync(new SomethingHappenedEvent { Id = message.Id });
return new MyNextRequestClass { Id = message.Id };
}
With NServiceBus, all these messaging operations are available via the handler’s IMessageHandlerContext
, and NServiceBus will get the messages where they need to go without requiring a change to the function definition itself:
async Task Handle(MyRequestClass message, IMessageHandlerContext context)
{
Log.LogInformation("Hey, a message with id {0} arrived!", message.Id);
await context.Publish(new SomethingHappenedEvent { Id = message.Id });
await context.Send(new MyNextRequestClass { Id = message.Id });
}
🔗Rapid prototyping
One of the biggest advantages to Azure Functions is the ability to easily and cheaply deploy a proof of concept solution and then scale it up to production, especially when you’re unsure of what scale the solution will ultimately need to fulfill.
Eventually, solutions that start off on Azure Functions may reach a level of maturity where hosting on Azure Functions is no longer preferable. The cost of Azure Functions, while flexible, may be higher than running the system on dedicated resources.
In this case, using NServiceBus as an abstraction layer over Azure Functions really pays off. The message transport (Azure Service Bus) stays the same, and the message handlers (classes that implement NServiceBus’s IHandleMessages
interface) all stay the same. Meanwhile, hosting can easily be shifted to WebJobs in an Azure App Service or to containers running on Azure Kubernetes Service. Only the minimal boilerplate code that creates endpoints out of queues is coupled to the Azure Functions stack.
🔗Other benefits
A few other benefits of integrating NServiceBus with Azure Functions include:
- Non-JSON serialized messages, including handling multiple serialization types.
- Message body or message property encryption and decryption.
- Message auditing
- Adding an additional message pre- or post-processing behavior is easy and powerful but doesn’t clutter up your business processing.
- Centralized error handling
- Provisioning the messaging infrastructure is easier with the help of our tooling for creating queues and subscriptions
🔗Introducing our Preview program
Our support for Azure Functions is being released as the first result of our new preview program.
But make no mistake: Preview does not mean substandard quality. Our goal is to be enterprise-grade, which means stability. All our previews are production-ready capabilities, but they’re licensed separately from the rest of our tools, and come with their own support policy.
We get a lot of requests to support new technologies with the Particular Service Platform. As you can imagine, it is hard to keep up with them all. Deciding what to add and when to add it has been hard. Over the last year or so we have been working internally on how to become more innovative while still maintaining the high standards we hold ourselves to. This is why we have created the new program we are calling Previews.
Previews are new software that either expands the technologies that our Platform integrates with or adds entirely new ideas to our platform. Since it is new that comes with some risk. While we do our research, we still are not sure if our customers are willing to adopt the software we release. The Preview program is designed to be the final test of any new innovation we have been exploring. By releasing a minimally featured version, with support in your production environments, we can validate that the solution we are bringing forward is the correct one and that it will be well received by our current and future customers.
The current Preview software we are offering and the status of each one can be found on our Platform previews page. Let us know if you are using any of our Preview software and keep an eye out here and on our twitter feed as we launch additional Previews.
🔗Summary
With support for Azure Functions, we are bringing the Particular Service Platform to the cost-efficient serverless environment. NServiceBus enables simple message handling while taking care of serialization, routing, error handling, recoverability, and other complexities of messaging in distributed systems.
For another look at NServiceBus and Azure Functions together, check out Azure Functions for NServiceBus by Simon Timms.
To get started with some code yourself, check out our Using NServiceBus in Azure Functions with Service Bus triggers sample. We also have a version of the sample for Azure Queues if that’s more your style.