Azure Functions are Microsoft’s answer to the serverless development model. Functions were introduced in March 2016, about two years after AWS released Lambda, the first and probably most popular way to build serverless applications.
I’ve worked with AWS Lambda extensively in the past, and I’ve been looking forward to familiaring myself with Functions to see how similar the two products are. As it turns out, while the concept of running serverless applications is similar, there are a lot more differences between the two than I expected.
Defining Azure Functions
Let’s start by defining the different parts of Azure Functions that I’ll be discussing in this article.
Azure Functions refers to the serverless hosting service as a whole that is available within the Azure Portal. You create Function Apps to execute small, short running Functions that run on machines that are managed (for the most part) by Azure. All Functions within a Function App share the same resources, runtime, scaling, pricing, and application settings.
The individual Functions are run when an event or schedule triggers them. Functions can easily interact with other resources on Azure and be orchestrated together in a number of ways.
Function Apps Should Do One Thing
There are many ways to organize Functions within Functions Apps. The rule of thumb, after discussing it briefly on Twitter with Anthony Chu, is to keep Functions that are part of the same business domain within a single Function App.
Using this approach, each Function App can focus on a single domain, and each Function can have a single responsibility within that domain. A Function App that focuses on a single domain won’t need to load as many assemblies into memory, resulting in a smaller memory footprint, which can potentially help lower costs.
There are a few other strategies that could be interesting for grouping functions. I’ll be writing a separate article to look at them in the near future.
Function Apps Can Be Orchestrated
There are a few ways to execute multiple Functions together:
- Durable Functions
- Queue or Topic based Communication
- Logic Apps
Durable Functions are the new kids on the block. They provide a clean and concise way through code to invoke other Functions to accomplish a larger task. Writing this type of Function will be second-nature for anyone used to working with async/await and its related Task-based methods. There are a number of architectural patterns that can be addressed with Durable Functions, as documented on Microsoft Docs.
Another common way to make Functions talk to each other is to leverage queues and topics to send messages back and forth between Functions. In this scenario, a Function writes a message to a queue that is read by another Function to signal that it can do its processing. This a common architectural pattern, but requires more overhead than Durable Functions since the queues and topics in question need to be created and configured for use with the appropriate Functions.
The final orchestration piece for Functions is Logic Apps. Logic Apps let developers build a workflow-like serverless application that invokes Functions based on conditionals and custom logic. There are connectors to many other components within Azure available as well, making it highly flexible.
The downside to Logic Apps is that the designer can be a bit unwieldy for large workflows. Logic Apps are backed by a JSON document, so they can be edited that way if needed. I’ve often thought that a YAML-based document would be easier to read and maintain for this type of thing, but this could just be my AWS experience talking.
Functions Apps Run In A Short Period of Time
Functions have a default timeout of 5 minutes, after which the Function stops running. That timeout can be increased to 10 minutes if necessary. It’s possible to run a Function for even longer than 10 minutes with an App Service Plan (see below), but if you find yourself in this scenario, my instinct would be to take a second look to determine if Functions are the right solution to the problem being solved.
Generally speaking, serverless applications are meant to run in a short period of time. Holding yourself to the 5 minute default timeout helps keep your Functions focused on the type of work they are best suited for.
Functions Apps Run In A Managed Environment
One of the main goals of serverless is to abstract away the infrastructure needed to run cloud applications, freeing developers to focus on solving business problems instead.
The beauty of this model is that you no longer need to provision machines, stay up-to-date with OS patches, install .NET, configure IIS, etc. The managed environment also takes care of scaling Function Apps for you. All together that’s a huge burden lifted, especially for smaller teams, and is one of the biggest advantages to serverless that I see.
Sorting Out All The Dials
There are many different ways to kit out Azure Functions. There are two pricing models, two runtimes, and two ways of writing C#-based Functions. All these options make Azure Functions highly flexible, at the expense of simplicity.
The easiest way to get started with Azure Functions is the Consumption Plan, where you pay only for running Functions, based on number of executions, execution time, and memory usage. The Consumption Plan will automatically scale a Function to additional instances when needed.
A Function that doesn’t run won’t cost a thing, making it a great plan for something that runs infrequently. The downside is that an unfrequently called Function will eventually have no running instances, and will incur a cold start the next time it’s invoked. A cold start of a Function is like starting your car on a cold winter morning: it takes a lot more effort to turn the engine on after it’s been sitting at -20C overnight. But once the engine is warm, starting the engine a second time will be much quicker. Similarly, Functions take longer to start their first running instance, but scale much faster from then on.
The App Service Plan is there for developers who need a bit more oomph for their Functions or want to leverage their existing App Service environments. App Service is a Platform-as-a-Service that lets you run cloud apps in a managed environment on dedicated virtual machines. The unused cycles of those VMs can be repurposed to run Azure Functions. Using the App Service Plan virtually eliminates cold starts, lets Functions run past the 10 minute timeout, and can run on more powerful hardware than the Consumption Plan.
Start with the Consumption Plan if you’re a newcomer to Azure Functions, and move to App Service if and when the needs present themselves. For existing App Service developers, the App Service Plan is most likely the way to go to maximize the usage of those virtual machines.
C# Script vs Pre-Compiled C#
There are a number of languages supported in Azure Functions. There are different reasons to use each of them, but I’m focusing only on the C#-based options here.
C# Script was developed in the early Roslyn days to write C# in a more dynamic, less ceremonious way than what previously existed. It can be used to write Functions directly in the Azure Portal or in a lightweight code editor like Visual Studio Code. I find them appropriate for the simplest of Functions or to quickly test out a proof of concept.
Pre-compiled C# will be more familiar for a majority of developers. Building a Function in this way is much like building any other type of .NET application. I favor them for writing production-grade code for a few reasons:
- They incur slightly shorter cold start times than their scripted cousins.
- They produce more structured and maintainable Functions.
- It’s quicker for existing C# developers to pick up.
The General Availability runtime for writing Azure Functions, V1, is built on the .NET Framework. V2 of the runtime, which is currently in Preview, is built on .NET Core.
Microsoft’s current recommendation is to write production-ready Functions only with V1 of the runtime, but there are many signs that the General Availability of the V2 runtime is imminent.
With that in mind, here’s how I would approach the creation of new Functions:
- If you have existing V1 Runtime Functions, continue writing V1 Runtime Functions.
- If your Functions need to interact with any other .NET Framework code or applications, continue writing V1 Runtime Functions.
- If you’re starting from scratch today, consider writing V2 Runtime Functions, so long as you’re ready to handle frequent breaking changes. The V2 Runtime should be GA by the time you’re ready to go to production.
Putting It All Together
Azure Functions is a powerful tool to rapidly build highly scalable cloud applications that require little operational overhead with minimal cost. Serverless, at least for me, is the true promise of the cloud.
Below are some of the references that helped me decipher all the inner-workings of Azure Functions. They dig deeper into the topics discussed above and provide examples where appropriate.