Azure Serverless - Local Development

27 September 2020
#Azure#Serverless#MSCreate

In the first of a three-part series covering Development, Deployment, and Troubleshooting for getting started with Azure Function Apps. This originally was going to be covered during a segment with @_mhoeger during the Create: Serverless event that took place on September 30th, 2020, but I was unable to attend at the last minute.

We will focus primarily on organization, configuring, and reporting issues and a sprinkling of using extensions for using bindings, so read on the intrepid learner.

Organizing

After working on a handful of projects that use function apps, I've found what I believe to be the best way ™️ to structure code so that it's both navigable and organized while working within the requirements for building Function Apps.

Required Structure

When defining a function, or using the command palette and Azure Extension, it's required to create a folder for defining functions that has a unique name and contents that are used executing with the function run time. This requirement can be limiting when organizing a project, regardless of language, as the project grows. An example project with an HTTP Triggered function named "GetFunction", we'd have the following layout for a Node.js based function, omitting the rest of the project files:

...
GetFunction/
  function.json
  index.js
...

As more are added to a reach a significant number of functions, more than four from my experience, then the project is going to start looking like the following:

...
PostFunction/
  function.json
  index.js
GetFunction/
  function.json
  index.js
DelFunction/
  function.json
  index.js
OptFunction/
  function.json
  index.js
PutFunction/
  function.json
  index.js
...

That example omits any of the other files required for a Function App project, like host.json, extensions.csproj, or any number of files for a project and as it can easily get cluttered unless some action is taken to keep the project manageable.

While I'd love to be able to define all the functions within a single function.json with metadata & binding definitions for Function Apps, we not yet there 😔. But we do have also have three optional changes to clean up the folders and the index.js/index.ts/etc for each function.

Folder Names

One change I adopted after a few projects is that while the individual folders are required, the names are arbitrary and a change to the folder's name is a quick and meaningful change. What I've chosen to add is a prefix about the project's function names, like ProjectZeta, to all folders or by adding a single letter, likeA_ or Z_ so that they are at the top or bottom of the file listing; API_ or GraphQL_ are also great options as they're descriptive and provide a common folder structure. When changing the folder name with some common prefix, makes it so they're grouped together and/or grouped in a certain order in the project structure, it is easier to navigate project files. Experiment with what works when playing with options, it can make larger projects easier to handle but be mindful of the following section as changing folder names doesn't change where the function.json looks for files for compiled languages.

scriptFile

Another behavior that we can take utilize is the function.json that serves to define both the entry point for code execution and what file to use. When working on a project that uses a scripting language like typescript or python (and probably Java), the scriptFile key is present that points to a specific file the function runtime will use.

{
  "scriptFile": "dist/myfile.py",
  "bindings": [ ... ]
}

Adding or changing the scriptFile key allows us to point to a different file for the function to use; what it will look for when scriptFile is defined is a default method that will start the function execution, but by default, it will look for the default exported depending on the language.

entryPoint

The other option we have is to point to a specific method/function from either the default file (like index.js) or if we've defined a scriptFile. Like with scriptFile, using entryPoint allows us to declare a name to use for our function, if there is a function ZetaV1 and defined in entryPoint, the function will use that over a default value.

Bugs

An important part of developing software beyond organization and architecture is staying up to date to prevent encountering bugs that reduce a project's reliability or to protect against potential vulnerabilities and improve security; reporting bugs can also help to address new issues and provide updates and fixes or workarounds. While updating has become a simpler process over time for VSCode and Function Apps, I'll run through the process and discuss issue reporting and the meta-repository that can be used to locate the specific project.

Where to report

When encountering a bug when developing, or worse when deployed, it's important to know where to try and resolve the problem or bring attention to a new or existing issue. Fortunately for developers, Microsoft has a sizable portion of Azure code base available on GitHub with issue trackers visible for the developers to follow and report issues to. The meta repo Azure/Azure-Functions provides a great starting point to determine where the issue should be created

Existing issues (yay!)

Sometimes when going to report an issue, it may be a known issue and someone else has created an issue already which is excellent! This means that the experience and logs will be able to provide additional context for the issue and help to amplify it so the team can focus on it or prioritize it. Another great upside is that because the issue has already been out there, the team responsible may have a workaround and would make it possible that to apply to keep working on whatever awesome feature or functionality that has been planned.

What to report

When reporting issues it's important to provide valuable and relevant information for troubleshooting, like the version of core tools being used, extension versions if not using extension bundles, and any information about Azure deployed resources permitted to share (some organizations don't permit sharing), and, if possible, steps to reproduce the bug.

Bug Checklist:

  • Versions of extensions, runtimes, and modules
  • Where the issue is encountered, locally or deployed
  • Expected behavior
  • Actual behavior
  • How to reproduce (not always possible)

Example Issue

I was facing an issue with a durable function app, which is an awesome addition to function apps and everyone should check it out, that was throwing an exception for the function runtime when it should've entered an error state within the JavaScript code. After working directly with an engineer on the team and found a way to reproduce the issue, they created the following issue azure/durable-functions#125 that is a great example of how to best structure an issue report. While they were on the inside track, creating issues like this helps the team narrow down the underlying cause. While the Azure/durable-functions repo does have a template for bug reports, not all do, so it's important to keep a basic checklist in mind when creating issues.

Why to report

Reporting an issue is a huge boon to that shares software that as it allows for problems to be captured and resolved sooner, while also helping to provide greater context to the underlying functionality. I've learned a good amount about how Function Apps operate when having discussions on issues, like how most languages pass and send data to a C# runtime that does all the heavy lifting, like connecting to all the bindings and triggers.

Staying Updated

The other side of bug and issue reporting is ensuring that the system being used is up to date for any of the required components. Whether the project is using C# or PowerShell, keeping up to date will help to avoid known issues & vulnerabilities that are present in older code so that time can be spent implementing over searching for a solution.

VSCode Extensions

One of the many great features of using VS Code and Azure Functions (preview) is that the extension will occasionally prompt to update core tools version when opening a function app project if a new version exists. Depending on the platform that is used for developing on, most packages may be done automatically by VS Code Extensions, but the following is a brief rundown that can be helpful if things are not updated automatically.

Function Core Tools

One of the required components used for development is the core tools package, which provides the func runtime, but the installation and updating processes are slightly different depending on the OS. The documentation of the installation process is located here and while packages like azure-functions-core-tools from npm can provide the same functionality, I prefer to using one the options listed within the Azure Docs.

Windows

Updating for Windows requires downloading and installing the appropriate .msi from the install docs

macOS

macOS is a straightforward update, a quick brew update and brew upgrade with the package version after the initial install will update azure-functions-core-tools.

Linux

Much like macOS, Linux will update the package with a simple sudo apt-get update and sudo apt-get upgrade with the package version installed (or are upgrading to)

npm

An alternative to OS-specific installation is to use npm to install the core tools package using npm i -g azure-functions-core-tools@2 --unsafe-perm true (to use V3, change to @3), I've used this way before and it works well, but in order to be consistent with the Azure Docs and for troubleshooting, I prefer to stick with the above documentation.

Language & Platform Specific packages

Alongside the function runtime provided by the core tools packages, there are going to be packaged like durable-functions for node or azure-functions-durable for python, that will require updating. In these cases, I prefer to defer the work to this VSCode extension that checks the most up to date version and will in most cases run the update command if it's a supported package management system; it also has its own GitHub to make issues and feature requests for. Though these packages don't need to be updated nearly as often unless there is a specific issue that's preventing work or want to target a new feature.

Extension Bundles

A decision that can be made when starting or later on in the project lifecycle, is whether to use extension bundles to simplify the dependencies of the Function App. With choosing not to use bundles, it will require installing any extensions to the app, like Microsoft.Azure.WebJobs.Extensions.CosmosDB for integrating with CosmosDB via Triggers and Bindings, but starting with Functions 2.x, only HTTP and timers are available by default.

While not using the Extension Bundles does require migration it does give developers more flexibility when choosing using extensions for Function Apps. I personally prefer to not use bundles and install extensions to each project I work on, that I can ensure a consistent deployment component; this does mean I need to upgrade manually using either a VSCode extension or manually.

Registering Function Extenstions

Migrating from extension bundles is easy, and is likely going to be done right after using VSCode to create new projects with the Azure Function extension.

Remove Bundles

Remove the extensionBundle property and it's associated values from the host.json for the project:

"extensionBundle": {
  "id": "Microsoft.Azure.Functions.ExtensionBundle",
  "version": "[1.*, 2.0.0)"
}

Install packages

Use func extensions install to install the NuGet packages locally, which will result in a bin/, obj/, and extensions.csproj added to the project; the function runtime will look at each of the functions defined on the project and install the appropriate package. I add the bin/ and obj/ folder to .gitignore or appropriate file to prevent git indexing and committing to source control and reduce the overall size of the repo.

Considerations

  • In the event there is a bug fix available in a specific version of a package, the new version will be available sooner than the extension bundles, similarly downgrading to a working version is much easier as modifying the extensions.csproj file to the specific version and rerunning func extensions install will install that version.
  • Defining an additional task within .vscode/tasks.json will provide a mechanism to install the packages whenever the function runs locally.
  • Using local files over the implicit dependency of the extension bundles, the runtime will look for the packages locally and will fail to start functions that depend on these files; it's critical that as part of the projects deployment strategy to install them before, especially when using ZipDeploy or Docker, running func extensions install as part of the process will ensure they are available.