Writing Azure Functions in F#

Breno Fatureto
Datarisk.io
Published in
6 min readMar 18, 2022

--

Photo by C Dustin on Unsplash

Introduction

Current web ecosystems afford a large variety of options for deploying web services. With such freedom comes a large burden: how to pick the option that best serves the needs of the application while balancing infrastructure and maintenance costs?

Azure Functions are a serverless computing platform provided by Microsoft. With Azure Functions, you can easily create and deploy web apps that run upon an specified trigger, such as a timer or an HTTP request. Since it’s a serverless platform, you do not have to worry about system administration or package management.

While the Azure Functions runtime allow you to deploy projects in a variety of technology stacks, at Datarisk we use them for deploying F# projects. The .NET environment also makes for a very good fit. F# is an OCaml-style functional-first language that provides static type checking with a Hindley-Milner system. We’ve elaborated on the use of F# at Datarisk in a previous post.

Documentation in creating and deploying Azure Functions written in F# is lacking; most documentation on Azure Functions focus on .NET projects in C#. In this post, we will see working steps for creating and running HTTP- and Timer-triggered Azure Functions written in F#. In the next article, we will see how to deploy an Azure Function.

Prerequisites

.NET 6

We start by installing our development platform. The Azure Functions runtime host version 4 requires .NET 6, which is the most recent version as of the time of writing (Dec/2021).

The easiest way to get started is with the dotnet-install.sh script.

curl -L https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh
chmod +x dotnet-install
./dotnet-install.sh -c 6.0

You can also check the .NET download page for more instructions.

Azure Function Core Tools

We also need to install the Azure Functions Core Tools. The project’s GitHub repo provides instructions for a variety of platforms. Make sure to install version 4. Here is the line to install using NPM.

npm i -g azure-functions-core-tools@4 --unsafe-perm true

If you’re on Arch Linux, there is also an AUR package.

Azurite

Another tool we will need is Azurite, which allows us to emulate the Azure storage provider locally. Some of the Azure function triggers, such as the timer trigger, require storage emulation.

The easiest way to get started is to run the Docker image provided by Microsoft.

docker pull mcr.microsoft.com/azure-storage/azurite
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite

Initializing a project

We use the Azure Functions Core Tools to create the project structure.

func init MyFunctionProj

We get a menu showing runtime options. For this project, pick 1 for dotnet

Select a number for worker runtime:
1. dotnet
2. dotnet (isolated process)
3. node
4. python
5. powershell
6. custom
Choose option: 1
dotnet

The tool creates a new directory called MyFunctionProj. A few changes to the project structure are needed though. First, we have to rename MyFunctionProj.csproj to MyFunctionProj.fsproj. We then have to manually alter this file so that host.json and local.settings.json are correctly copied to the output build directory. To accomplish that, we have to rename the Update parameter to Include. We also add a rule so that local.settings.json isn't pushed over when you publish your function. Your MyFunctionProj.fsproj file should look like the following.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
</ItemGroup>
<ItemGroup>
<None Include="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="local.settings.json" Condition="Exists('local.settings.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>

We also need to download a few file templates for dotnet.

dotnet new --install Microsoft.Azure.WebJobs.ItemTemplates

Creating a simple HTTP triggered Azure Function

An HTTP-triggered Azure Function will run upon receiving an HTTP request. With these functions you can create, for example, an API or a webhook response service.

To start out, create a file named SimpleHttpTrigger.fs with the following contents.

namespace AzureFunctions.Function

open System
open System.IO
open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Logging

module SimpleHttpTrigger =
[<FunctionName("SimpleHttpTrigger")>]
let run ([<HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)>]req: HttpRequest) (log: ILogger) =
async {
log.LogInformation("F# HTTP trigger function processed a request.")
let responseMessage = "Hello world!"
return OkObjectResult(responseMessage) :> IActionResult
} |> Async.StartAsTask

Upon receiving an HTTP request, the run project will run and provide the sample Hello world! response.

Next we set up the project so that this new file is built. Inside the last ItemGroup in the MyFunctionProj.fsproj, add the following line.

<Compile Include="SimpleHttpTrigger.fs" />

At this point, we are able to run dotnet build and get a successful build. We can finally start the function with func host start. You should get some output that looks like this.

Functions:

SimpleHttpTrigger: [GET,POST] http://localhost:7071/api/SimpleHttpTrigger

For detailed output, run func with --verbose flag.

If you run curl http://localhost:7071/api/SimpleHttpTrigger, you should get a response with the text Hello world!, and the func process should output a log for the request.

[2021-12-21T19:27:36.233Z] Executing 'SimpleHttpTrigger' (Reason='This function was programmatically called via the host APIs.', Id=659d45f1-159a-4266-afb4-4beee4f6247a)
[2021-12-21T19:27:36.250Z] F# HTTP trigger function processed a request.
[2021-12-21T19:27:36.273Z] Executed 'SimpleHttpTrigger' (Succeeded, Id=659d45f1-159a-4266-afb4-4beee4f6247a, Duration=55ms)

Creating a Time-Triggered Azure Function

Now we’ll check out another type of Azure Function trigger: a timer trigger. This trigger allows us to run our function on predetermined time intervals.

We start by generating the file using the previously downloaded templates.

dotnet new timer --language F# --name TimerTrigger

This command will create a file TimerTrigger.fs. Next we set up the project so that this new file is built. Inside the last ItemGroup in the MyFunctionProj.fsproj, add the following line.

<Compile Include="TimerTrigger.fs" />

Note that the file created by the template includes a typo: the function is named TimerTriggerCSharp. You can change the function name to TimerTriggerFSharp for correctness sake.

namespace Company.Function

open System
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Host
open Microsoft.Extensions.Logging

module TimerTrigger =
[<FunctionName("TimerTriggerFSharp")>]
let run([<TimerTrigger("0 */5 * * * *")>]myTimer: TimerInfo, log: ILogger) =
let msg = sprintf "F# Time trigger function executed at: %A" DateTime.Now
log.LogInformation msg

The time interval on which the function will run is specified in a string using cron syntax. The string is placed in the [<TimerTrigger("0 */5 * * * *")>] attribute. In this example, we define that the function will run every 5 minutes. To be more specific. the string "0 */5 * * * *" specifies that the function will run on the 0th second of every 5th minute. We can make it run every minute by changing the string to "0 */1 * * * *". This syntax allows us to specify arbitrary time intervals. For example, "0 30 9 * Jan Mon" specifies 9:30AM every Monday in January.

At this point, you should be able to run dotnet build and get a successful build. Before we can start the function, we need to start the Azurite for storage emulation. The following commands will pull the Docker image and start it.

docker pull mcr.microsoft.com/azure-storage/azurite
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite

You can also add the -d flag in the docker run command (before the -p) to run in the background.

Finally, run func host start to launch your function. You should now see some activity in the terminal running Azurite. After a while, your timer-triggered function should run and print some output.

[2021-12-08T16:40:00.060Z] Executing 'TimerTriggerFSharp' (Reason='Timer fired at 2021-12-08T13:40:00.0195735-03:00', Id=eff3c63f-20ec-4432-b4a5-076c0592e27a)
[2021-12-08T16:40:00.108Z] F# Time trigger function executed at: 12/8/2021 1:40:00 PM
[2021-12-08T16:40:00.126Z] Executed 'TimerTriggerFSharp' (Succeeded, Id=eff3c63f-20ec-4432-b4a5-076c0592e27a, Duration=96ms)

Conclusion

In this post we’ve written a couple of Azure Functions in F# and ran them locally. Note that we did not have to write any code relating to how our application is served or when it runs; these are all taken care of by the Azure Functions platform.

In the next post, we will take it a step further and deploy an Azure Function to the cloud.

Thanks to Eduardo Bellani, Vinicius Gajo, and Guilherme Marz for reviewing and providing feedback for this article.

At Datarisk, we have a dedicated development team keen on custom data processing solutions and technologies. We are ready to help you create a robust solution. You can check our other posts here, and also visit our site to see how we solve real world problems. Also feel free to reach out to me at LinkedIn.

--

--

Breno Fatureto
Datarisk.io

Functional programming enthusiast. Software developer at Datarisk. https://brenoafb.com/