Azure Serverless - Local Development
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 rerunningfunc 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
orDocker
, runningfunc extensions install
as part of the process will ensure they are available.