Writing Azure Functions in F#
--
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.