As software developers, there are often times where we need to quickly add new functionality to an existing system. One common way to achieve this is by using serverless architecture. We're going to take a look at how we can utilize Azure Functions, a part of the Microsoft Azure Serverless Platform, to help us do this. Azure Functions is a serverless service that allows you to quickly build the functionality you need in your Azure environment without having to build out and manage a full-scale system. Along with Logic Apps and Event Grid, Azure Functions make up the Azure Serverless Platform.
This past October I had the opportunity to go to TechBash in the Pocono Mountains in Eastern Pennsylvania. There were a lot of interesting topics covered throughout the course of the week. One of the topics that stuck out to me as getting some noticeable extra attention was Azure Functions (check out Jeremy Likness' post on his talk here and docs from Santosh Hari's talk here.) This post is a high-level introduction to this powerful technology.
WHAT WE'LL DO
(follow along by cloning the code here)
We'll get comfortable creating Azure Functions to meet some basic needs by building a simple ordering system. Let's say we're tasked with implementing an online ordering system for a local deli, Rocchio's Deli. Rocchio's Deli has an existing website, but would like their customers to be able to place orders online so their employees can prepare it before they come in to the deli. We'll keep it simple, but we need to build three pieces of functionality to get a basic ordering system up and running for them.
Allow users to create orders and store them in our system with a confirmation number
Instantly notify the Rocchio's Deli staff when a new order is created
Allow the Rocchio's Deli staff to get a list of recent orders created in our system as a starting point
We'll meet these three needs by building the system illustrated below.
On the back end of our system, we’ll create three main Azure Functions that we'll take a look at (and one function not in the diagram to handle getting the SignalR Hub connection information). The first function, StoreOrder, will handle an HTTP POST of an order and store that order in a Cosmos DB database collection. Azure uses the Cosmos DB Change Feed to listen to our Cosmos DB collection for any changes and outputs the updated documents as JSON in the order in which they were modified. This is what will feed the BroadcastOrders function. This function will use a CosmosDB trigger to retrieve the inserted order documents as JSON from the change feed. Then it'll send a SignalR message to all registered clients (Rocchio's Deli staff) with the new order information via an Azure SignalR Service. The staff will need to register to our SignalR hub by getting the connection information via a GetSignalRConnectionInfo function (not in diagram). The GetOrders function will handle an HTTP GET request and return the 20 most recent orders stored in our CosmosDB collection to give the staff a recent history of orders.
Finally, we'll create a client app that the Rocchio's Deli staff can use to interact with our Azure backend system.
CREATE AND STORE THE ORDERS
Let's take a look at the StoreOrder function first. This will set up an endpoint that the current Rocchio's Deli website can post orders to. Our Azure Function class has a method named "Run" that takes decorated parameters that describe the functionality they contain. The StoreOrder Run method takes an HttpTrigger expecting to be POSTed to. The Route param to the HttpTrigger is set to null here. This defines the endpoint that we'll use to trigger this function. Specifying null as we've done here uses the function name as the endpoint. This function will be triggered by posting to /api/StoreOrder. Here we're expecting an HttpRequest parameter which gives us access to the request body. Our second parameter is an IAsyncCollector of dynamic objects. This is our output binding. Any items we add to this will automatically be inserted in to our Cosmos DB collection. We specify the database and collection to insert to with the CosmosDB attribute. Note that you'll need to set a "CosmosDBConnection" to your Cosmos DB connection string (to quickly set up a Cosmos DB connection locally, get the emulator here. Once our input and output parameters have been set, the bulk of the work is done. We can parse the request body for the order, perform some processing on the order and insert it as a new document in our database collection.
NOTIFY STAFF OF NEW ORDERS
The BroadcastOrder function will notify the Rocchio's Deli staff of new orders. This function is triggered by an insertion/update in our CosmosDB database. We specify an IReadOnlyList of Document to handle the modified database documents, and we decorate it with a CosmosDBTrigger attribute with all of the CosmosDB information it needs to connect to it. For the output parameter, we'll need to bind to a SignalR hub. Thanks to Anthony Chu's contribution to the Azure Function ecosystem, we can do that easily. We'll need to make sure the Microsoft.Azure.WebJobs.Extensions.SignalRService package is installed first. Also, ensure that the "AzureSignalRConnectionString" app setting is configured with a valid Azure SignalR Service connection string. This package allows us to hook in to an Azure SignalR Service instance via our Azure Function. To do this, we set our output parameter, again, to an IAsyncCollector. This time, however, it will contain SignalRMessages. We decorate the output parameter with a SignalR attribute, specify a name for our hub and that's it! We can now handle our CosmosDB document changes and send out SignalR messages. Notice that the the SignalRMessage's Target property is the event name that our clients will listen for in order to receive the messages. The SignalRMessage's Arguments property contains the data that we're sending. For more information on the Microsoft.Azure.WebJobs.Extensions.SignalRService package, go here.
Now that we've set up our "orders" SignalR hub to send out "broadcastOrder" messages, we'll need to give the staff application a way to get the information they need to connect to our "orders" hub. We do this here with the GetSignalRConnectionInfo Function. This has an HttpTrigger as an input binding to give the clients an endpoint to request the information from. The second parameter hooks in to our "orders" hub and gives us access to the SignalRConnectionInfo the clients need to register for messages from that hub.
GET RECENT ORDERS
Finally, for our last Azure Function, we'll set up another HTTP endpoint for the staff application to get the 20 most recent orders. This will give them a starting list of orders. Again, we'll have an HttpTrigger as an input binding. Our second parameter will be a CosmosDB binding, but this time we specify a SqlQuery to perform on the specified database collection, and the parameter type will be an IEnumerable of Document. This runs the specified query on our database and gives us the result in this second parameter.
RUN THE FUNCTIONS
That's it! With just this minimal amount of code, we're ready to run through our system. Run the project to test what we have. Visual Studio may prompt you to download and install the Azure Functions Core (CLI) tools if you don't have them installed. Accept and install them. You should see the Azure Functions runtime output below. You'll see that it lists the endpoints of the HttpTrigger Functions that are running. You can test these in Postman to verify that they're all running as expected.
CLIENT APP FOR STAFF
Now that we've verified that the endpoints are working correctly, let's take a look at the client application that the Rocchio's Deli staff will be using. The application will need to do three things.
Load the last 20 orders to give a starting point and display the order data to the user
Register to the "orders" SignalR Hub that we set up in our Azure serverless environment
Handle incoming orders as they're created from the "broadcastOrder" event from our SignalR Hub
The client app is a Vue app that hits the GetOrders endpoint to get the last 20 orders to display as a list, and adds each incoming order to the list as they're received. All of the above functionality can be found in the App.vue file. To enable the app to register to our SignalR Hub, we make a request to the SignalRConnectionInfo function endpoint and register with the URL and access token we receive. To enable us to handle the incoming orders as they're broadcasted, we create and start a new HubConnection, and register to process the "broadcastOrder" events that come from our "orders" hub.
AZURE FUNCTIONS PROS
Azure Functions are quick and easy to implement. They use input and output bindings to interact with your system. This means that you can declaratively tell your function how you want it to interact with your system. You can use an HTTP input binding to have a function handle an HTTP request as we've seen in this example. You can also use a Timer trigger to tell your function to run every n hours, a Blob Storage trigger to run code to process files as they're uploaded to your Azure Blob Storage, or a Queue trigger to fire when messages are submitted to an Azure Storage queue. Check out all of the supported bindings here under "Reference->Bindings". Once you get a couple of the common bindings under your belt, you'll find it easy to quickly spin up a function that can do a myriad of useful things and can be dropped right in to your current architecture.
Another benefit of using Azure Functions is that they're cheap. Check out an overview of the pricing here. You can also use the Azure pricing calculator here. At the time of this writing, the first 400,000 GB/s of execution and 1,000,000 executions are free. As a quick example, if you have a function that uses 128 MB of memory and takes 2 seconds to run. Executing it 3 million times in a given month will set you back a whopping $6. Not bad.
AZURE FUNCTIONS CONS
One potential downside of Azure Functions that all serverless platforms share is that they can "go to sleep" if they're not triggered for a set amount of time. Functions that are "awake" will process immediately, but functions that have been idle for some set amount of time will go to sleep and can take several seconds to minutes to wake back up. This is something to keep in mind especially if you're planning on using them to handle user interactions that require an immediate response.
With minimal time and effort, we've been able to build a way for clients to interact with our system and with each other. While this example only provides basic working functionality of an ordering system, it's enough to get started on your way to diving deeper into building fully-developed Azure serverless systems. See below for links to further learning and next steps.
To learn more about integrating Azure Function with the Azure Event Grid, go here.
A notable omission in the above examples is the lack of authorization. To learn more about using Authorization keys with your Azure Functions, go here.
To check out using recommended best practices with Azure Functions, go here.