Skip to content

brettrowberry/OpenFSharp2019-UnitConversionService

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Unit Conversion Service Workshop

Let’s see how F# language features make it simple to create a unit conversion service and deploy it to Azure Functions.

Module 0: Background

Q: Who made this Workshop?
A: Brett Rowberry, who works at ExxonMobil

Q: When was this workshop first given?
A: Friday September 27th, 2019 at Open F# in San Francisco

Q: How long does this workshop take?
A: 110 minutes

Q: What prerequisites will I need?
A:

Q: Why Azure Functions?
A:

  1. Why serverless?
    • Potentially less expensive
    • Potentially a simpler programming model
  2. Why Azure Functions and not AWS Lambda or other serverless offerings?
    • I use Azure at work

Q: What are the programming/deployment models supported by Azure Functions?
A:

  • Script
  • Compiled (what we'll use in this workshop)
  • Container

Module 1: C# Azure Function

There isn't an official F# template at the moment, so we'll start with a C# tutorial.

  1. Open the module1 directory in Visual Studio Code
  2. Navigate to Create your first function using Visual Studio Code
  3. Here are the main sections that we will go over together:
    1. Prerequisites
    2. Create your Functions project using Visual Studio Code's Command Palette
      • Accept all the defaults
    3. Run the function locally and call it
    4. Publish the project to Azure using Visual Studio Code's Command Palette
      • Use the basic publish option (not advanced), Azure Functions: Deploy to Function App...
      • Name your app module1<yourname>
      • Use the Azure region closest to you. We'll use West US region since we're in San Francisco.
  4. Call the deployed API

Module 2: F# Azure Function

  1. Open the module2 directory in Visual Studio Code
  2. Create the C# project again, this time using the Azure Functions extension GUI
    • The button looks like a folder with a lightning bolt and the tooltip says Create New Project...
    • Change the function name to HttpTriggerFSharp
    • Accept other defaults
  3. Navigate to Azure Functions With F#. Thank you Aaron Powell for your post and for allowing us to use it in this workshop!
    1. Copy the code to the source file and change the extension from .cs to .fs (Ionide might look really upset at the file for a while, don't worry!)
    2. Change the extension of the project file from .csproj to .fsproj
    3. In the .fsproj file below the first <ItemGroup> section paste
<ItemGroup>
  <Compile Include="HttpTriggerFSharp.fs" />
</ItemGroup>
  1. Run it to make sure it works
  2. POSTs aren't very fun to test. Let's change the function to a GET that uses query parameters like in Module 1.
    • Paste over the code with
namespace Company.Function

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

module HttpTrigger =
    [<FunctionName("HttpTrigger")>]
    let Run([<HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = null)>] 
            req: HttpRequest,
            log: ILogger) 
            = 

        let stringValues = req.Query.Item "name"

        if StringValues.IsNullOrEmpty stringValues
        then
            log.LogInformation("no name was passed")
            BadRequestObjectResult("Include a 'name' as a query string.") :> ActionResult
        else 
            let name = stringValues.[0]
            log.LogInformation(sprintf "name was '%s'" name)
            OkObjectResult(name) :> ActionResult
  1. Run the function locally and call it.
    • Note that we switched the authorization from Function to Anonymous
  2. Publish the project to Azure using the GUI
  3. There will be a prompt to stream logs, accept it
  4. Call your app, inspect the logs
  5. Navigate to https://portal.azure.com
  6. Select your Function App
  7. Disable and reenable the app
  8. Run a test

Module 3: Unit Conversion Service

  1. Open the module3 directory in Visual Studio Code
  2. Create the same project as in Module 2
    • Name the app UnitConversionAPI
    • This time we'll use route parameters instead of query parameters
    • Here's the code:
namespace API

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

module Length =
    [<FunctionName("Length")>]
    let Run([<HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "Length/{source}/{target}/{input}")>] 
            req: HttpRequest,
            source: string,
            target: string,
            input: string,
            log: ILogger) 
            = 
        let inputs = String.Join("|", source, target, input)
        log.LogInformation(sprintf "Inputs: '%s'" inputs)
        inputs 
  1. Run the function locally and call it
  2. Now that we have a working app, let's implement the conversion logic. Add a file above the existing file named Length.fs
namespace UnitConversion

open System.Collections.Generic
open System.Linq
open System

module Length =
    let private lengthsAndFactors =
        let fsharpDict =
            dict [
                "meter", 1.0
                "millimeter", 1e-3
                "kilometer", 1e3 ]
        Dictionary<string, float>(fsharpDict)

    let private tryGetUnitFactor name =
        match lengthsAndFactors.TryGetValue name with
        | true, factor -> Some factor
        | _ -> None

    let private lengths = 
        let lengths = lengthsAndFactors.Keys.ToArray()
        String.Join(", ", lengths)

    let convert source target input =
        match (tryGetUnitFactor source, tryGetUnitFactor target)  with
        | None, Some _ ->
            sprintf "Length unit '%s' not found. Try %s." source lengths |> Error
        | Some _, None -> 
            sprintf "Length unit '%s' not found. Try %s." target lengths |> Error
        | None, None -> 
            sprintf "Length units '%s' and '%s' not found. Try %s." source target lengths |> Error
        | Some s, Some t -> 
            input * s / t |> Ok
  1. Change your functions file to be:
namespace API

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

module LengthAPI =
    open UnitConversion.Length
    [<FunctionName("LengthAPI")>]
    let Run([<HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "length/{source}/{target}/{input}")>] 
            req: HttpRequest,
            source: string,
            target: string,
            input: float,
            log: ILogger) 
            = 
        let inputs = String.Join("|", source, target, input)
        log.LogInformation(sprintf "Inputs: '%s'" inputs)
        
        match Length.convert source target input with
        | Ok result ->
            log.LogInformation (sprintf "Conversion result: %f" result)
            OkObjectResult result :> ActionResult
        | Error msg ->
            NotFoundObjectResult msg :> ActionResult
  1. Run the function locally and call it
  2. Publish the project to Azure and call it
    • Name your app module3<yourname>

More resources

About

A simple unit conversion service that runs on Azure functions.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages