Skip to content

Instantly share code, notes, and snippets.

@mathias-brandewinder
Created September 19, 2016 19:41
Show Gist options
  • Save mathias-brandewinder/ad23c0ec27c8fc8fec08c2a5167a51b3 to your computer and use it in GitHub Desktop.
Save mathias-brandewinder/ad23c0ec27c8fc8fec08c2a5167a51b3 to your computer and use it in GitHub Desktop.
intro to Azure Functions
  • title : Azure Functions
  • description : Introduction to Azure Functions with F#
  • author : Mathias Brandewinder
  • theme : night
  • transition : default

An intro to Azure Functions with F#


Disclaimer

  • Just started using Azure Functions last week

Goal

  • General (read "basic") introduction
  • Some tips & tricks to get you started

Plan

  • What are Azure Functions?
  • An example: @fsibot
  • Hands-on introduction

What are Functions?

Process events with serverless code

Azure Functions is a serverless event driven experience that extends the existing Azure App Service platform. These nano-services can scale based on demand and you pay only for the resources you consume.

Azure functions


"Serverless Event Driven"?

  • A Function is a script which contains, well, a function - F#, C#, or Node.JS
  • Triggers define what event trigger the function execution
  • Runtime is based on WebJobs SDK, manages execution and scaling
  • A Function App is a collection of Functions

Anatomy of a Function

This is a function, defined in filefoo.fsx:

open System.Environment

let Run(timer: TimerInfo, log: TraceWriter) =

    log.Info("Function was triggered!")

... where is the timer coming from?


Bindings

A file function.json in the same folder defines bindings:

{
  "bindings": [
    {
      "schedule": "0 * * * * *",
      "name": "timer",
      "type": "timerTrigger",
      "direction": "in"
    }
  ],
  "disabled": false
}
  • uses a CRON expression {second} {minute} {hour} {day} {month} {day of the week}
  • defines the timer using as input (in) in the Run function.
  • every binding contains at least type, direction, name

Bindings (cont'd)

  • Timer
  • Storage (Blog, Queue, Table)
  • HTTP/webhook
  • Service Bus
  • Notification Hubs, Mobile Apps, Event Hubs, DocumentDB...

What if you need a Nuget package?

Define your dependencies in a project.json in the same folder:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        ""FSharp.Compiler.Service": "6.0.1"
      }
    }
  }
}
  • Some common dependencies are already there for you (Json.NET for instance)

How do you write & deploy code?

  • App Service Editor
  • Continuous deployment
  • Kudu (whatever that is)
  • FTP

Pricing

METER PRICE FREE GRANT (PER MONTH)
Execution Time $0.000008/GB-s 400,000 GB-s
Total Executions $0.20 per Million Executions 1 Million Executions
  • GB-s: memory size in Gigabytes of your app by the sum of execution time in seconds
  • this is preview price

Before looking at code...

  • Functions are in preview
  • Development experience in App Service Editor is currently primitive
  • Classic vs. Dynamic
  • F# support improving by the day (perf, documentation)

Question so far?


A "practical" example: @fsibot

@fsibot is a side-project of mine; you can send F# code to @fsibot on Twitter, and it will evaluate the expression and respond with the result.

Example 1
Example 2

Original version used F#, deployed as Windows Service(s) using TopShelf, on a VM Rewrote entirely using Azure Functions last weekend


Application

Application flow:

  • did anyone mention @fsibot on Twitter?
  • retrieve Tweet, User and Message
  • extract code and run it using F# Compiler Service
  • respond to the Tweet

Architecture

3 functions:

  • fsibotmentions: probe for mentions every 2 minutes, send them to a Queue "mentionsQueue"
  • fsibotcompiler: dequeue message from "mentionsQueue", try to compile, send result to a Queue "responseQueue"
  • fsibottweets: dequeue message from "responseQueue", format response, send response

Guided Tour


Statelessness

  • Twitter mentions: how do I know if a mention has already been processed?
  • I cannot keep in memory the ID of the last processed Tweet
  • Solution: read / write to a blob
  • Heavy? Perhaps. But arguably good (fault-tolerant) design

Messages

So far I have found the easiest approach is to

  • create types for messages
  • serialize/deserialize using JSON
  • represent messages as strings in the function signature
  • note: outgoing messages use byref

Resources

Azure Functions
F# documentation
Greg Shackles
Michał Niegrzybowski
The great @fsibot caper


Hands on

... let's create a simple demo function!


Creating a function app

  1. Go to the Azure Portal: https://portal.azure.com
  2. Select + New > Function App > Create
  3. Select a name (ex: mathias-sfsharp-09-2016), subscription, new Resource Group (ex: mathias-sfsharp-09-2016), Classic App Service Plan, Pin to dashboard
  4. Be patient... (2 minutes?)

Creating a function

  1. Select New Function > Language F# > Empty, name it "TimerFunction"
  2. You should now have in Code:
open System

let Run(log: TraceWriter ) =
    log.Verbose("F# function executed.");

Bindings

  1. We need to add some bindings. Go to Integrate > New Trigger > Timer > Select. Leave the timer named "myTimer", the Schedule as is, and Save.
  2. Go back to Develop. Under Code, click "View Files". Select "function.json", you should see the following bindings:
{
  "bindings": [
    {
      "type": "timerTrigger",
      "name": "myTimer",
      "schedule": "0 * * * * *",
      "direction": "in"
    }
  ],
  "disabled": false
}

Getting the function working

  1. Select run.fsx, and modify the code:
open System

let Run(myTimer: TimerInfo, log: TraceWriter ) =

    log.Info("F# function executed.")

Getting the function working (cont'd)

  1. Hit Save (which should now be red). In the Logs window, you should see the following:
2016-09-18T00:54:39.233 Script for function 'TimerFunction' changed. Reloading.
...
2016-09-18T00:54:40.201 Compilation succeeded.
2016-09-18T00:55:00.002 Function started (Id=cebd8264-49f4-4f28-9e72-abac866f05b3)
2016-09-18T00:55:00.002 F# function executed.
2016-09-18T00:55:00.002 Function completed (Success, Id=cebd8264-49f4-4f28-9e72-abac866f05b3)
  1. If you give it a minute, you should see a second message
2016-09-18T00:56:00.003 F# function executed.
2016-09-18T00:56:00.003 Function completed (Success, Id=9a3a6acc-78bc-4802-8c53-d83521be58b0)

... your timer function is now up-and-running :)


Using configuration

  1. Let's use some configuration. Under "Search my functions", below "Monitor", click "Function app settings" > Configure app settings. In the app settings section, add a Key and Value, for instance "Hello" and "World", and save on the top-left corner.

  2. Close by clicking [X] in the top-right corner

  3. Go back to TimerFunction, and edit the code in run.fsx:

open System
open System.Configuration

let Run(myTimer: TimerInfo, log: TraceWriter ) =

    let appSettings = ConfigurationManager.AppSettings    
    let value = appSettings.["Hello"]
    log.Info("Hello, " + value)
    log.Info("F# function executed.")

Using configuration (cont'd)

You should now see in the log:

2016-09-18T01:02:33.796 Script for function 'TimerFunction' changed. Reloading.
...
2016-09-18T01:02:35.280 Compilation succeeded.
2016-09-18T01:03:00.002 Function started (Id=3cf142c4-8993-4de2-b81d-ee54e54e5c8f)
2016-09-18T01:03:00.002 Hello, World
2016-09-18T01:03:00.002 F# function executed.
2016-09-18T01:03:00.002 Function completed (Success, Id=3cf142c4-8993-4de2-b81d-ee54e54e5c8f)

dependencies

  1. Let's add a nuget package, say, FSharp.Data version 2.3.2. Go to Show Files, click +, add a file called project.json, paste in the following:
{
  "frameworks": {
    "net46":{
      "dependencies": {
        "fsharp.data": "2.3.2"
      }
    }
  }
}

Dependencies (cont'd)

You should see in the logs something like this:

2016-09-18T01:13:27.926 Restoring packages.
2016-09-18T01:13:27.926 Starting NuGet restore
2016-09-18T01:13:28.941 Restoring packages for D:\home\site\wwwroot\TimerFunction\project.json...
2016-09-18T01:13:29.316 GET https://api.nuget.org/v3-flatcontainer/fsharp.data/index.json
2016-09-18T01:13:29.582 OK https://api.nuget.org/v3-flatcontainer/fsharp.data/index.json 268ms
...
2016-09-18T01:13:37.267 Packages restored.
2016-09-18T01:13:37.579 Script for function 'TimerFunction' changed. Reloading.
2016-09-18T01:13:39.938 D:\home\site\wwwroot\TimerFunction\run.fsx(4,9): warning FS1182: The value 'myTimer' is unused
2016-09-18T01:13:39.938 D:\local\Temp\tmp4047.fsx(9,38): warning FS988: Main module of program is empty: nothing will happen when it is run
2016-09-18T01:13:39.938 Compilation succeeded.

Dependencies (cont'd)

  1. We can now use fsharp.data in run.fsx:
open System
open System.Configuration
open FSharp.Data

let Run(myTimer: TimerInfo, log: TraceWriter ) =

    let appSettings = ConfigurationManager.AppSettings    
    let value = appSettings.["Hello"]
    log.Info("Hello, " + value)

    let data = WorldBankData.GetDataContext()
    let capital = data.Countries.France.CapitalCity
    log.Info("Capital of France is " + capital)

    log.Info("F# function executed.")

Queues, Blobs, Tables

  1. Storage: how to create and use a Queue? Go to Integrate > Outputs > Azure Storage Queue > Select, and in the Storage Account Connection, select new; you should see here the storage account that was created for you as you created the function.

That's it for now :)

  1. You can Disable or Delete the function in Manage, and look at some more logs under Monitor.

... thank you!


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment