F# on AWS Lambda

F# on AWS Lambda

On the 1st December AWS announced support for C# in AWS Lambda which is, by all accounts, great news! However being a bit of an F# fanatic I wondered if, as it does support .Net Core, we could host an F# project as well?

Building Blocks

All code in hosted on GitHub here

Let's start with the smallest project we can:

fsharp-lambda-01

F# is still not fully supported by .Net Core so you have to include some additional tooling in the form of dotnet-compile-fsc within the project.json file like so:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "compilerName": "fsc",
    "emitEntryPoint": false,
    "compile": { 
      "includeFiles": [
        "Script.fs"
      ]
    },
    "debugType": "portable"
  },
  "tools": {
    "dotnet-compile-fsc": {
      "version": "1.0.0-*",
      "imports": [
        "dnxcore50"
      ]
    }
  },
  "dependencies": {
    "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-*"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.1"
        }
      },
      "imports": "dnxcore50"
    }
  }
}

We'll want to ensure we've got the correct NuGet feeds set up too, so in the nuget.config we have:

<configuration>
  <packageSources>
    <add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
    <add key="AspNetCI" value="https://www.myget.org/F/aspnetcirelease/api/v3/index.json" />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  </packageSources>
</configuration>

Finally, our entry point Script.fs is a very basic Hello World application:

namespace AwsLambdaTemplate

module Program =
    open System
    open System.IO
    open System.Text

    let handler = 
        printfn "Hello World!"
        0

To test this locally we need the .NET Core SDK which sets up the dotnet cli tool on our path.

You'll also need to ensure you have the .Net Core 1.0.1 SDK installed (currently the above link will give you 1.1.0).

Now on the command line in the root directory we can run:

dotnet restore
dotnet build 
dotnet run

Getting Ready For AWS Lambda

OK, so we've got a basic F# project running on .Net Core 1.0, how do we get it into the cloud?

First we want to setup the AWS Tools for Windows Powershell; this will let us manage our credentials and services from the command line.

Next we need to add the AWS Lambda dependencies to our project.json file. Firstly the dependencies section needs some new libraries:

  "dependencies": {
    "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-*",
    "Amazon.Lambda.Core": "1.0.0*",
    "Amazon.Lambda.Serialization.Json": "1.0.0",
    "Amazon.Lambda.Tools": {
      "type": "build",
      "version": "1.0.0-preview1"
    }
  },

Then the tools section needs one small addition:

  "tools": {
    "dotnet-compile-fsc": {
      "version": "1.0.0-*",
      "imports": [
        "dnxcore50"
      ]
    },
    "Amazon.Lambda.Tools": "1.0.0-preview1"
  },

Running dotnet restore again will pull all these dependencies down for us.

Now if you run dotnet lambda help you'll see helper functions for deploying the function to the cloud.

Authenticating With AWS

Now our environment is ready, we need to set up authentication so we can push the function live. As we've got the Powershell tools for AWS we can do this on the cli like so:

Set-AWSCredentials -AccessKey {MyKey} -SecretKey {MySecret} -StoreAs AwsLambdaTemplate 

To get an Access Key and Secret; log into AWS Console, click your name in the top right and click Security Credentials. From there expand the Access Keys section and click "Create New Access Key".

Now we can run:

dotnet lambda list-functions --region eu-west-1 --profile AwsLambdaTemplate

This should return no results, if it returns a credential exception we've not set up the profile correctly.

Modifying The Code For Lambda

Lambda works by calling functions within our assembly by a defined "Handler" in the format Assembly::Namespace.ClassName::MethodName. This means our normal Program -> Main entry point won't work.

Step one then is to modify our project.json to stop emitting an entry point by modifying this property "emitEntryPoint": false.

Then we'll update our Script.fs to this format:

namespace AwsLambdaTemplate

module Program =
    open System
    open System.IO
    open System.Text
    open Amazon.Lambda.Core

    let handler(context:ILambdaContext) = 
        printfn "Hello World!"

Now that's done, we're ready to package the project for publishing. For reference our handler string will now be FSharpLambdaTemplate::AwsLambdaTemplate.Program::handler

Packaging Our Project

This is as simple as running.

dotnet lambda package --configuration Release --framework netcoreapp1.0

This outputs a zip file to bin\Release\netcoreapp1.0\FSharpLambdaTemplate.zip

Setting Up The AWS Lambda Function

Returning to AWS Console, we now need to create an AWS Lambda Function to host our project.

Go to Services -> Lambda -> Get Started Now. Here select the Blank Function blueprint, then select no triggers (we'll trigger it ourselves) and click next.

Then we set up the function like so; note the zip file we packaged is selected:

fsharp-lambda-02

Then we'll tell AWS where our handler function is (The full handler string is FSharpLambdaTemplate::AwsLambdaTemplate.Program::handler) and how much resource to give the Lambda:

fsharp-lambda-03

I've left everything else as default for now. So we'll click next then Create Function.

Fingers Crossed

We'll click the Test button on the UI which will manually trigger the function. Hopefully, if all is well, the method in the assembly will execute and output "Hello World!" in our logs.

Does It Work?

YES!!!!!!!

fsharp-lambda-04

SERIOUSLY YES!!!

fsharp-lambda-05

Next Steps

Well we've now got a Template for F# on Lambda on GitHub which should help getting started with other projects.

I'd like to look into building handlers for events on Lambda (S3 uploads, SNS & Kinesis messages etc) and see if we get any language issues from doing that.

But all in all I'm going to get a beer, so happy AWS Lambda can use F# :-)