Let's Play with Azure Functions

Let's Play with Azure Functions

I saw a great talk last night at F# |> Bristol by Anthony Brown on how to use Microsoft Azure and F# to build a search engine (full talk here).

The talk incorporated Azure Web Jobs, Azure Search, Resource Manager & MBrace. However let's forget all that, the real meat here, the thing that got the room heaving with excitment, was Azure Functions.

Serverless Architecture is definitely the current Cloud "Buzz-Word", and with good reason. It takes the standard PaaS model and abstracts away everything into "Please run this script in response to X event". Amazon have it with AWS Lambda, Google have it with Cloud Functions & Microsoft now have it with Azure Functions.

During the talk Andrew showed us how his F# functions could respond to messages from Azure Service Bus along with acting as a HTTP API responding to commands without any infrastructure to worry about. What impressed me the most was that the mapping of the endpoint was convention based, rather than in AWS land where you have to build an API Gateway front end (heavy on the configuration) first.

I'm so excited to try this that, barely a day later, I'm going to give it a go. Wish me luck.

Getting set up

I've already got a Microsoft Azure account from previous spikes, it's free to get one set up here if you'd like to give this a go.

From here I login to the portal and am presenting with the increasingly attractive Azure UI.

AF01

Here we're going to click the "+ New" icon and search for a Function App

AF02

I'll use the standard settings and call it "tallmansplayground"; which gives me an endpoint at "tallmansplayground.azurewebsites.net".

AF03

Ok that raised a conflict, looks like I have to use a specific App Service Plan. You can create one of these instead of using "Dynamic Service Plan" when creating a Function App.

I've configured mine to use the Free Tier for now.

AF05

That worked! Great, let's make some functions.

AF06

Building our first Azure Function in F#

From the new dashboard I've clicked "+ New Function". There's a load of templates, even some in F#!

AF07

This "HttpPOST(CRUD)-FSharp script looks interesting. We know during the setup wizard that we've already got a storage account attached to this app, so let's have a play with that.

AF08

The code is generated for us, that's nice, along with a url, which is also nice.

#r "System.Net.Http"
#r "Microsoft.WindowsAzure.Storage"

open System
open System.Net
open System.Net.Http
open Microsoft.WindowsAzure.Storage.Table
open FSharp.Interop.Dynamic

type Person() =
    inherit TableEntity()
    member val Name: string = null with get, set

let Run(req: HttpRequestMessage, outTable: ICollector<Person>, log: TraceWriter) =
    async {
        let! data = req.Content.ReadAsAsync<obj>() |> Async.AwaitTask
        let name = data?name

        if isNull name then
            return req.CreateResponse(HttpStatusCode.BadRequest,
                "Please pass a name in the request body")
        else
            let person = Person()
            person.PartitionKey <- "Functions"
            person.RowKey <- Guid.NewGuid().ToString()
            person.Name <- name
            outTable.Add(person)

            return req.CreateResponse(HttpStatusCode.Created)
    } |> Async.StartAsTask

Interestingly it's also given us a code in the URL which maps to a "Function Key". This is in the query string in the format ?code={function-key} and presumably allows us to secure our function early on.

You can configure this by changing the "Authorization level" of the "HTTP (req)" trigger to Anonymous

In the code I'm seeing an auto-generated person object but no actual reference to the destination table in Azure Table Storage.

Looking in the Integrate tab I see that it's actually the output of the Run function mapped into the table, rather than direct access from the script. This is also nice.

AF09

AF10

On trying to run the template as generated I get this error:

FSharp.Interop.Dynamic:
Could not load file or assembly 'Dynamitey, Version=1.0.2.0, Culture=neutral, PublicKeyToken=cbf53ea3aeb972c6' or one of its dependencies. 
The system cannot find the file specified.

A bit strange considering we're using a template from the portal. I also got a few timeouts when testing this in the form of:

We are unable to reach your function app.
Your app could be having a temporary issue or may be failing to start. 
You can check logs or try again in a couple of minutes.

This may be due to my cheap-arse use of the "Free Tier" but was certainly unexpected!

Finally got there in the end, with some modification to the original script:

#r "System.Net.Http"
#r "Microsoft.WindowsAzure.Storage"
#r "lib/net45/Newtonsoft.Json.dll"

open System
open System.Net
open System.Net.Http
open Microsoft.WindowsAzure.Storage.Table
open Newtonsoft.Json

type Person() =
    inherit TableEntity()
    member val Name: string = null with get, set

let Run(req: HttpRequestMessage, toSave:ICollector<Person>, log: TraceWriter) =
    async {
        let! json = req.Content.ReadAsStringAsync() |> Async.AwaitTask
        let data = JsonConvert.DeserializeObject<Person>(json);
        log.Info(sprintf "%A" data.Name)

        let person = Person()
        person.PartitionKey <- "Functions"
        person.RowKey <- Guid.NewGuid().ToString()
        person.Name <- data.Name
        toSave.Add(person)
        return req.CreateResponse(HttpStatusCode.Created)
    } |> Async.StartAsTask

AF11

I also had to remove the Interop dependency from the project.json file and clear out the validation code (isNull wasn't even defined!) and add Newtonsoft.Json as the MediaTypeFormatter for Json didn't seem to be properly registered (it could never retrieve the Name I submitted.

Building our second Azure Function in F#

Now we've got a post endpoint we can write one to get the data out too.

There is a template function to do this already too

AF12

It, like the POST function, is also massive broken! After a bit of tweaking I got it working with this script:

#r "System.Net.Http"
#r "Microsoft.WindowsAzure.Storage"

open System.Linq
open System.Net
open System.Net.Http
open Microsoft.WindowsAzure.Storage.Table

type Person() =
    inherit TableEntity()
    member val Name: string = null with get, set

let Run(req: HttpRequestMessage, inTable: IQueryable<Person>, log: TraceWriter) =
    let names = query {
                    for person in inTable do
                    select person
                } |> Seq.fold(fun acc person -> acc + (sprintf "%s " person.Name)) ""

    req.CreateResponse(HttpStatusCode.OK, names)

There's an inbuilt runner configuration you can use which saves switching back to Postman all the time. As you can seen, it is now working!

AF13

Afterthoughts

The key advantages I can see here are in the Integrate tab where you can set up how each function responds to requests and where it's output should be persisted.

The power of being able to route from a HTTP request directly into Azure Table Storage with only a few clicks is certainly prominent, with other supported outputs being DocumentDB, Event Hub, Blob and Service Bus to name but a few.

A bit disappointed that the F# Templates didn't work at all. Hopefully over time these will stabilise and present an easier setup experience for developers.