Combining Suave and Azure Storage

I previous wrote about my initial experience with F# and showed some characteristic operators and functionality. I think F# and functional programming is fun and efficient to work with, so I would like to build an application that uses F# where possible.

See F#: TypeProviders + Basic Operators

Some time back I created a web application for keeping track of my wines. It is a pretty simple asp.net + react.js app which I worked on when learning react+redux. The data is stored in an Azure storage table. I would like to extend/rewrite this application to make use of F# where feasible.

Wine app picture

In this post I will demonstrate how to read data from an Azure storage table and serve it through an api, all in F# using the Suave web server. Later, I intend to build further on this, eventually re-writing the wine-app, learning new technology and techniques on the way.

As F# and .NET core runs perfectly on mac and unix based systems, I will try to do everything on macOS, without the need to boot Windows! .NET and mono are all you need. For development vim-fsharp or vscode with ionide are solid choices.

Setting up the project

When installing dotnet SDK, the dotnet cli command is made available. This can be used to quickly scaffold new .NET-projects. The dotnet new-api supports allot of different project types with more being released as we speak. The idea is that if you need a new web project, a library or some specific framework project (e.g. NServiceBus messaging service), this can be setup with a single command.

For our simple web app we'll need a console application to start. This can be created with the following command.

dotnet new console -lang f#

This will give us a plain F# project targeting .NET-core 2.0 with a single Program.fs-file without any logic. Since the API will use the Suave web server, the application needs a way of obtaining packages from Nuget. For this we will use paket, a package manager for .NET and mono projects, allowing referencing of packages from Nuget as well as directly from git repositories or HTTP sources.
Adding paket to the project is as simple as downloading the paket-bootstrapper executable and running the init script.

mkdir .paket
curl -L github.com/fsprojects/Paket/releases/download/5.114.5/paket.bootstrapper.exe \
 -o .paket/paket.exe
mono .paket/paket.exe init

This will generate a couple of files. paket.dependencies is used to specify the top level dependencies of the solution, from which later can be referenced into specific projects. For the API in this example we will add Suave, Json and Azure storage as dependencies.

#paket.dependencies

source https://nuget.org/api/v2
nuget Suave
nuget Newtonsoft.Json
nuget WindowsAzure.Storage
clitool Microsoft.DotNet.Watcher.Tools

After creating this file we can run paket, which will install the packages specified in the paket.dependencies-file.

mono .paket/paket.exe install

This will generate a paket.lock-file, which specifies the concrete dependency resolution tree of all the installed packages. This file will be checked in as part of the source code, making sure that the exact same package versions are restored on consecutive builds. The packages are downloaded to a package directory and can be used directly from a script or referenced into a project.

A paket.references-file can be created to include references to the packages in the project file. Running mono .paket/paket.exe restore will then update the .fsproj file created earlier, making the dependencies available to our code.

#paket.references

FSharp.Core
Suave
Newtonsoft.Json
WindowsAzure.Storage
Microsoft.DotNet.Watcher.Tools

Suave Web Api

Next up is adding some code to the generated Program.fs-file. Suave makes it easy for us to get a simple web server up and running, answering to HTTP requests.

open System
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful

[<EntryPoint>]
let main argv =
  let app = 
    choose [
      GET >=> choose [
        path "/api/wines" >=> getAllWines connectionString
      ]
      POST >=> choose [
        path "/api/wines/" >=> addNewWine connectionString
      ]
    ]
        
  let config =
    { defaultConfig with 
        bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8085 ]
    }
    
  startWebServer config app
  0

The Suave app consists of a single function app which is composed from several other functions. These functions has the type WebPart, defined in Suave. A WebPart is a function taking a HttpContext as argument, which contains the request content, and returns a HttpContext option. The returned type can be either Some HttpContext or None which will be used to decided routing etc.

The choose-keyword decides whether the webpart will be activated or not. First choose will match the request against GET or POST deciding the request type. If either is a match, choose will try to match a url path, and invoke the specified workflow.

To test the web server we can use dotnet run together with dotnet-watch. This will start up the web server and detect when changes are made to restart the program.

dotnet watch run
Started
[08:42:48 INF] Smooth! Suave listener started in 89.165 with binding 0.0.0.0:8085

Railway oriented programming

When a request matches one of the defined routes, the path-function will return Some(HttpRequest), which is passed to the next operation in the pipeline. In this example the getAllWines-function, which again returns either None or Some. As seen, Suave uses the >=>-operator to chain these functions together. This pattern is called railway oriented programming, which is a way of connecting functions that return some option type. Let's say we have the following function:

let railway = f1 >=> f2 >=> f3

If any of the functions returns None, the railway function will stop execution of the chain and return early. This technique of composing functions, together with the choose-keyword we can build a decision tree for routing of requests.

For the api we would like to create a function that retrieves all the stored wines from the database.

let getAllWines connectionString (ctx: HttpContext) = async {
  let! wineList = Database.getWineList connectionString "Anders"
  return! Successful.OK (JsonConvert.SerializeObject(wineList)) ctx
}

The function is passed a connection string and the context, then calls the database, which is an async workflow, and returns the result. The let!-keyword is syntax for awaiting a async workflow and assigning the result to a variable. return! is similar, returning the result directly without the assignment.

Azure Storage Connection

The last piece is the database connection, which will connect the application to a Azure storage account.

First we create an Azure table client, and get the reference to the table containing the wine data. The connection string can be passed as an argument on startup or read from a config file at run time.

let getWinesTable connectionString = async {
  let client = (CloudStorageAccount.Parse connectionString)
    .CreateCloudTableClient()
  let table = client.GetTableReference "winestest"
  do! table.CreateIfNotExistsAsync() 
    |> Async.AwaitTask |> Async.Ignore
  return table 
}

The table client can then be used to create a query to fetch the rows for the given key.

let getWineList connectionString userName = async {
  let! results = async {
    let! table = getWinesTable connectionString
    let query = TableQuery.GenerateFilterCondition(
      "PartitionKey", QueryComparisons.Equal, userName)           
    return! table.ExecuteQuerySegmentedAsync(
      TableQuery(FilterString = query), null) |> Async.AwaitTask 
  }
  results
}

The api can now be browsed, dotnet watch has made sure the last version of the code is running. Next we can extended the api with more web parts for all the actions we need for the web application to function.

The code can be browsed at https://github.com/spinakr/WineVue/tree/plain-api.

Continuation

In the next post I plan to create a web app, served through the same web server, consuming the api with the data from Azure. We will create web parts for serving the static files of a web application, as well as a token based authentication web part.

References

The examples from these blog posts are inspired by the demo repository SAFE-BookStore. The SAFE-stack is completely written in F# using Fable and Elmish in addition to Suave and Azure Storage.