Moving a Bot Framework Bot to Azure Functions
Once your Microsoft Bot Framework Bot is built and deployed it may be can be costly to keep it online 24/7 using a cloud VM, especially if usage is low. Thankfully there's an easy way to convert your bot to a serverless architecture, in the form of Azure Functions, reducing those costs still keep the bot available whenever needed.
In my last post I spoke about how I converted an ASP.NET site to a serverless model using Azure Storage. Continuing in my endeavours to make a number of my lesser-used projects cheaper to keep online I turned my attention to Caardvark, a chatbot that allows users to send postcards to anyone, anywhere in the world for $1.99. It seamlessly walks you through the process of providing your image, recipient, their address and payment after which the card is printed and posted by Lob.com (more about how the bot was built with the help of a friend can be read here). Caardvark has currently only been used by a relatively small number of people and it really functions as a portfolio piece, but I am keen to keep it online as long as possible. The bot, as with many of the Bot Framework samples, was built using ASP.NET Web API which requires a VM. I’m currently hosting it using an Azure App Service plan which it shares with some of my other projects, but the price is still around £45 a month - not cheap! However, converting the code to Azure Functions has provided a much cheaper alternative.
For who haven’t encountered Azure Functions, it is Microsoft Azure's serverless computing offering, the equivalent of Amazon’s Lambda Functions and Google’s Cloud Functions. Functions offer a compute-on-demand model, meaning that once you've packaged the functionality you need into discrete "functions" you can deploy them and Microsoft seamlessly takes care of executing and scaling them on demand. You can handle one request one hour and 100,000 the next without having to configure anything. Functions are event-driven and can be triggered in a number of ways: by an HTTP request, on a schedule or a new Azure Queue Message. Because Azure charges per execution you only pay for what you use and are not charged when your application is idle.
The ASP.NET Web API Bot Endpoint
The first version of Caardvark was built using the Bot Framework examples so the Web API endpoint which receives messages from the framework, processes them and replies looks like this:
Above the class declaration you can see the
BotAuthentication attribute which handles authenticating all requests. The
POST endpoint below then receives an
Activity from the Bot Framework and either decides to refer the message to our conversation code or handle it differently for messages which, for example, could indicate the user is typing a reply which we won’t respond to.
WebAPIConfig file that resides in the
App_Start folder of the project is where the bot is configured, including setting where conversation history will be stored (Azure Table Storage in this case) and defining service implementations used for dependency injection in the conversation dialogs:
This code is run on startup and the dependencies are resolved using AutoFac that comes with the Bot Builder SDK.
So these are the main parts that we'll need to edit to convert the bot to work on Azure Functions:
Dependency and bot configuration on start
Handling messages and invoking dialogs
Converting to Azure Functions
The endpoint that the Bot Framework makes API requests to resides at
https://api.com/api/v1/messages on the messages controller in the Web API project. However, function apps do not have controllers so each endpoint has to be implemented as an individual function with an HTTP trigger which calls the function every time a matching request is made. Functions can reside in the same file and it is possible to have a single function that accepts multiple REST verbs so POST and GET requests could be handled differently inside the same function.
The Function and HTTP trigger I created for the bot initially looked like this:
Here the route is set to be exactly the same as before using the HTTP trigger. The AuthorizationLevel needs to be set to Anonymous to allow the endpoint to be accessed by anyone - the request will be authenticated properly inside the function like this:
First the bot service is initialised and then we use a method provided by the Bot Builder SDK that can verify the request is authorised and will return an error if the required request headers and tokens are not provided.
Now that the bot is secured, the code to do the bot configuration (which in the previous implementation resided in the App_Start file) needs to be added. Due to the stateless nature of functions you’ll need to do this on every request (whereas for our ASP.NET API it was only done once at startup). This will have some impact on the time it takes to perform every request, but I've found it isn’t noticeable to the end user:
I can then call this in our function just before we deserialise the request content and pass it to the section of code that manages the conversation and returns a dialog to the user:
In the end the complete function looks like this:
And there it is - the bot will now be serverless and you can host it very cheaply and efficiently using Azure Functions and, providing you keep down the number of dependencies you use, without degrading the experience for the end user even after a cold start.
If you're interested in reading more about what is possible with Azure Functions and serverless computing, you really must follow Troy Hunt on Twitter - in August 2018 he announced HaveIBeenPwned was fielding 54m requests for $32 a month using Azure and Cloudflare Functions - crazy numbers.