Introduction to Azure Functions
The Azure Functions platform allows you to run a piece of code (basically a simple function) in response to a wide variety of events; for example, you can run code when a client makes an HTTP request or when someone puts a message into a queue.
There are two fundamental concepts to understand when approaching the world of Azure Functions—triggers and bindings:
- Triggers are what cause a function to run; they define what kind of event the function responds to. A trigger tells you how a function is called, and a function must have exactly one trigger. Triggers have associated data, which is often provided as the payload of the function, and you can use the data contained in the payload to better understand the nature of the event that wakes up your function.
- Bindings are the way functions can exchange data with other cloud services such as Storage, Queue, or Cosmos DB. Bindings are defined in a declarative way: this means that you declare what kind of binding you need (for example, you might want to manage data with a Cosmos DB instance or write data into Azure Table storage) and the Azure Functions runtime provides the actual instance to the function to manage the data.
An Azure Function can have multiple bindings, and every binding can manage data input or output (or both—binding data can be received by the function or sent by the function to the connected services). A trigger can have only the input data (a trigger only receives data from the connected services and cannot send data to the services).
In version 1.x of the Azure Functions runtime, triggers and bindings were included in the runtime itself, while in version 2.x, triggers and bindings must be added as extensions, using NuGet packages. This means that you can reference only triggers and bindings you actually need and don't need to have all the supported triggers and bindings available for Azure Functions. Furthermore, the extension approach used by version 2.x allows you to create your own triggers and bindings. The advantage of being able to implement your own triggers and bindings is to delegate to the runtime the creation and the life cycle of the instances used by the function. In essence, the runtime acts as a dependency resolver for the instances required for the function. In runtime version 2.x, the only triggers available without adding extensions as NuGet packages are HttpTrigger and TimeTrigger; they are included in the Azure Functions SDK.
At the time of writing this book, the languages supported by Azure Functions (in version 2.x) are C#, F#, JavaScript, Java, Python (in preview), and PowerShell. The following table shows all the languages supported by the related frameworks:
The Azure Functions runtime is also designed to support language extensibility; that is, it's possible to create your own language worker process to manage your own programming language. The JavaScript and Java languages are built with this extensibility.
As for version 1.x of Azure Functions, a function app is a container of your functions. In version 2.x of the Azure Functions runtime, unlike what happened in version 1.x, a function app can host functions written in a single programming language.
An Azure Function written in C# is a static method decored by the FunctionName attribute:
public static class SimpleExample
{
[FunctionName("QueueTrigger")]
public static void Run(
[QueueTrigger("inputQueue")] string inItem,
[Queue("outputQueue")] out string outItem,
ILogger log)
{
log.LogInformation($"C# function processed: {inItem}");
}
}
FunctionName allows you to set the name of the function that is the function entry point. The function name must be unique in your project (that is, the function name must be unique in a function app). It starts with a letter and only contains letters, numbers, _, and -, and is up to 127 characters in length. You can call the method what you like—the Azure Functions runtime doesn't care about the name of the method but only the name of the function defined in the FunctionName attribute. The project template often creates a method called Run but you can change it.
The method signature can contain more parameters other than the one that defines the trigger. You can have the following:
- Input and output bindings marked as such by decorating them with attributes. In the previous sample, the Queue attribute declares that the string (the actual type bound by the runtime) will be saved (because it is an out parameter) in a storage queue called outputQueue when the method ends.
- An ILogger or TraceWriter (TraceWriter is available only for version 1.x) parameter for logging.
- A CancellationToken parameter for graceful shutdown in an async function.
- Binding expression parameters to get trigger metadata.
The arguments of the attribute support binding expressions that provide the possibility to use values defined in the app settings or retrieve special information from the binding.
For example, consider this sample:
public static class BindingExpressionsExample
{
[FunctionName("LogQueueMessage")]
public static void Run(
[QueueTrigger("%queueappsetting%")] string myQueueItem,
DateTimeOffset insertionTime,
ILogger log)
{
log.LogInformation($"Message content: {myQueueItem}");
log.LogInformation($"Created at: {insertionTime}");
}
}
The name of the trigger queue is defined in the app settings using the queueappsetting key, as shown in the next snippet:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"queueappsetting" : "inputqueue"
}
}
While the value contained in the insertionTime parameter is retrieved from the message creation time in the storage queue.
Once you write your function, you can build it (you'll learn how later in the chapter) and the build process will generate a folder structure like the following diagram:
As you can see, the build tool creates, as usual, the bin folder where you can find all the mandatory assembly for your code. Then, it also creates two essential files:
- function.json: In this file, you can find the metadata of the single function in terms of triggers, bindings, and assembly. For example, if you build a project that contains the previous LogQueueMessage function, the function.json file will look like this:
{
"generatedBy": "Microsoft.NET.Sdk.Functions-1.0.24",
"configurationSource": "attributes",
"bindings": [
{
"type": "queueTrigger",
"queueName": "%queueappsetting%",
"name": "myQueueItem"
}
],
"disabled": false,
"scriptFile": "../bin/MyFunction.dll",
"entryPoint": "MyFunction.BindingExpressionsExample.Run"
}
The configurationSource property tells the runtime that the code uses the attribute approach for trigger and bindings while the bindings section contains the complete definition for each binding involved in the function.
- host.json: This file contains the global configuration for the entire function app. For example, you can find the runtime version to use for hosting functions here. If you are using .NET Core 2.x, you can also put your configuration data in appsettings.json or local.settings.json (only for local test and debugging).
The entire Azure Functions ecosystem is open source, and a set of GitHub repositories contains all the different components related to the Azure Functions world. Here is the list of GitHub repositories currently available:
- Azure Functions: https://github.com/Azure/Azure-Functions
- Azure Functions host: https://github.com/Azure/azure-functions-host
- Azure Functions portal: https://github.com/azure/azure-functions-ux
- Azure Functions templates: https://github.com/azure/azure-functions-templates