Playing with the Fn project
Tags: fnproject, serverless, beginners, programming
Published: 2018-05-18 22:49:59

---
title: Playing with the Fn project
published: true
description: Getting starting with Fnproject, functions and serverless
tags: fnproject, serverless, beginners, programming
---

I am not one of those lucky ones who can simply read a whitepaper/code/docs and can quickly figure out how things work without trying things out in practice. I need to install things, run things and play with them to get a feeling for how stuff works. If I get stuck, that’s when I am going to read the docs and code to see what I am missing.

In this edition I decided to figure out how Fn project works, how to run it, use it and even extend it. The Fn project is the container native, cloud agnostic serverless platform.

As part of this article, I’ve created three different example extensions for the Fn server — you can get them on [GitHub](https://github.com/peterj/fn-extensions).

# The Basics — install and run
This section goes through installing the Fn server, starting it and then creating a simple function and invoking it.

You will need Docker on your machine in order to run Fn and once you have that, you can install the Fn with brew:

```
brew install fn
```

> Apparently I’ve installed Fn before and the Docker image I had on my machine was > stale causing Fn fail when starting — running `fn update` will ensure you have
> the latest images on your machine.

Finally, with Fn installed and images updated, you can run the Fn server with the following command:

```
fn start
```

The above command runs Fn in a single server mode with embedded database and queue. Behind the scenes, fn start command runs a Docker image calledfnproject/fnserver in a privileged mode. It also mounts the Docker socket into the container as well as the /data folder in the current working directory (this is where database and queue information is stored). Finally, it exposes port 8080 to the host, so you can invoke it on that port.

![docker ps](https://cdn-images-1.medium.com/max/800/1*z3us6YbBtLWWHPQ-OVHckg.png)

Now that you have the Fn server running, you can create a new function.

# First function
The Fn CLI comes with a init command that is used for creating new functions.

> At the time of writing, these were the upported function runtimes: dotnet, go, 
> java8, java9, java, lambda-nodejs4.3, lambda-node-4, node, php, python, 
> python3.6, ruby, rust, kotlin

Before we start, here’s a simple explanation of different concepts Fn uses:

**Apps** 
Apps are a way to logically group your functions under the same name (e.g. greeter-app)

**Routes**
Each function lives under a route in an app (e.g. /greeter-app/hello or /greeter-app/goodbye)

**Images**
Docker image that packages your function; the image used depends on the language of the function (e.g.fnproject/go, fnproject/ruby, fnproject/node, …), the goal here is that the image is as small as possible to be more performant

**Calls**
Call holds information about a call that was made to the function. It includes information about the app, route and time call was created, started and completed include the status of the call.

With this out of the way, let’s create a new function by providing a runtime (e.g. Go, Node or other supported language) and the name of the function:
```
fn init --runtime go hello
```

Above command creates a Go function in the hello sub folder. The function structure looks like this:

```
hello
├── Gopkg.toml
├── func.go
├── func.yaml
└── test.json
```

The source of your function lives inside the func.go file and has a function handler that responds with a “Hello World” message. The `func.yaml` file has information such as version runtime, name and entry point for your function.

Another interesting file is `test.json` — this file holds an array of tests (input values and expected output values) and you can use it to test out your function, by running `fn test`.

To run this function, you can use the `fn run` command. Before you run the command, make sure you set the `FN_REGISTRY` environment variable to your Docker repository.

Then when you run the command, Fn will build the Docker image with the function and runs the function like this:

```
$ fn run
Building image hello:0.0.1 ...........
{"message":"Hello World"}
```

This is all great, but we have the Fn server running locally, so let’s deploy our function to the server, instead of just running it.

To deploy the function, you can use the fn deploy command, specify the app name and add the `--local` since the Fn server is running locally:

```
fn deploy --app myapp --local
```

Command deploys the app (called `myapp`) to the local Fn server and it creates a path called `/hello` (our function name).


![running fn deploy](https://cdn-images-1.medium.com/max/800/1*U9UHXdQlrCScQr6wLn4nsg.png)

This means that on the Fn server, the function will be accessible under /myapp/hello path. The app name is used to logically group functions together. To see the full list of all routes defined on the Fn server, run this command:

```
# List all routes for 'myapp'
$ fn routes list myapp
path   image        endpoint
/hello hello:0.0.3  localhost:8080/r/myapp/hello
```

Finally, if you access the endpoint, you will get back the “Hello World” message like this:

```
$ curl localhost:8080/r/myapp/hello
{"message":"Hello World"}
```

## Grouping functions
To group the functions together, you can use the app name construct — this allows you to logically group different routes together (e.g. `greeter-app` could have routes called `/hello` and `/goodbye`).

In this case the `greeter-app` could also be the folder name where your functions live and subfolders `/hello` and `/goodbye` would contain the actual functions. You can also define the `app.yaml` file in the app root folder, to be able to deploy all functions with one command.

Follow the steps below to create a greeter-app with hello and goodbye functions:

```
# Create the greeter-app folder
mkdir greeter-app && cd greeter-app
# Create app.yaml that defines the app name
echo "name: greeter-app" > app.yaml
# Create a hello function in /hello subfolder
fn init --runtime go hello
# Create a goodbye function in /goodbye subfolder
fn init --runtime go goodbye
```

With all this set up and `app.yaml` in the root folder, you can use this command to deploy all functions to local Fn server:

```
fn deploy --all --local
```

Above command creates the following app and endpoints:

```
$ fn routes l greeter-app
path     image         endpoint
/goodbye goodbye:0.0.2 localhost:8080/r/greeter-app/goodbye
/hello   hello:0.0.2   localhost:8080/r/greeter-app/hello
```

You can also create a function that lives in the root of your app by running `fn init` command from the apps’ root folder:

```
fn init --runtime node
```

Now we have three functions under the `/greeter-app` logically group:

```
$ fn routes l greeter-app
path     image             endpoint
/        greeter-app:0.0.2 localhost:8080/r/greeter-app
/goodbye goodbye:0.0.3     localhost:8080/r/greeter-app/goodbye
/hello   hello:0.0.3       localhost:8080/r/greeter-app/hello
```

# Enabling the UI
If you prefer UI to interact with the Fn — there’s that for you as well. Assuming you have the Fn server running locally, you can start the UI like this:

```
docker run --rm -it --link fnserver:api -p 4000:4000 -e "FN_API_URL=http://api:8080" fnproject/ui
```

When image gets downloaded and container executes, you’ll be able to access the UI on `http://localhost:4000`.


![fn project UI](https://cdn-images-1.medium.com/max/1200/1*enzDJZLDo-7DB85i8sBSGQ.png)

# Extending Fn
There are a couple of different options for you to extend the Fn server. All options require you to rebuild the Fn server as you will have to import your extension — you can either use the build-server CLI command and `ext.yaml` file to build a new image of the Fn server with your extension(s) OR you can fork & clone the Fn repo and reference your extension in `cmd/fnserver/main.go` file, then re-build the code and run it.

For development, the fastest way is to clone the Fn repo and create & register your extension there. If you are using `build-server` command it might take a bit longer as that command will re-build the Fn server image each time it’s invoked. Note that you will have to build the Fn server each time in both cases, but the straight-up Go build is much faster than rebuilding a Docker image.

There are three extension points on the Fn server: listeners, middleware, custom API endpoints. Read on for a more detailed description of each extension point and look at some examples later in the article.

## Listeners
You can listen to various API events and respond to them. There are 2 types of listeners at this moment: App and Call. I think Route listeners should come soon as well…

In an App listener, you can respond to the following events:

```
BeforeAppCreate
AfterAppCreate
BeforeAppUpdate
AfterAppUpdate
BeforeAppDelete
AfterAppDelete
```

These events are available in a Call listener:

```
BeforeCall
AfterCall
```

## Middleware
With middleware you can add desired functionality for every API request that comes to the server. Within that middleware you can then decide if you want to cancel the request or if you want to call the next middleware in the chain. A simple example of a middleware would be an authentication middleware that checks headers for a token or a middleware that logs certain things for each request.

## Custom API endpoints
Custom API endpoints allow you to add new endpoints to the Fn server. For example, you could add a custom API endpoint that handles requests to a custom route such `/mycustomroute` or define an endpoint with route `/v1/apps/:app_name/mycustomhandler` or `/v1/apps/:app_name/routes/:route_name/mycustomhandler`.

For example, one could implement a custom endpoint on apps and routes called `stats` (so, `/v1/apps/:app_name/stats and /v1/app:app_name/routes/:route_name/stats`) and when those endpoints are invoked you could return some basic stats for the app or a route.

# Example: Call counter extension using Call listener

I wrote a simple extension that counts the number of times an app has been called and it outputs that number to the stdout. You can get the source code for the extension here.

The extension implementation is separated into two files: `callcount.go` and `calllistener.go`.

In the first file (`callcount.go`) I register the extension and set up the call listener like this:

{% gist
https://gist.github.com/peterj/71f22eb1577263ba6dce8d3d74f30bf2
%}

In the `init` function, I am creating a map called `callCountMap` that I’ll use to increment the calls to specific app and then I am registering the extension by calling `RegisterExtension` function and passing in my extension struct that implements `Name` and `Setup` functions. In the name function I am simply returning just the import name where the extension is located at and in the `Setup` function I am actually adding the `Call` listener, telling Fn that I’ll be listening to Call events (these events are implemented in the `calllistener.go` file):

{% gist
https://gist.github.com/peterj/611fbd4b48b1339a50de8e9733d812e8
%}

In the `BeforeCall` function we check if there’s an entry with the `AppID` in the map, and if it isn’t, we set the number of calls to 0. Similarly, in the `AfterCall` function we increment the number of calls for the `AppID` and prints out that number.

With the extension ready we can modify the Fn server to include our extension. There are two things we need to do in the `cmd/fnserver/main.go` file:

1. Import the extension like this (line in bold):

```
import (
    "context"
    "github.com/fnproject/fn/api/server"
    **_ "github.com/peterj/fn-extensions/callcount"**
)
```

2. Call `AddExtensionByName` in the main function:

```
func main() {
    ctx := context.Background()
    funcServer := server.NewFromEnv(ctx)
    funcServer.AddExtensionByName("github.com/peterj/fn-extensions/callcount")
    funcServer.Start(ctx)
}
```
Now we can build the fnserver and run it to try out the extension.

## Try out the extension

Let’s run the command below to rebuild the fnserver:
```
go build -o fnserver ./cmd/fnserver
```

Finally, run the `./fnserver` and when it starts, try calling a function you’ve deployed earlier. You should see the “Call number: X” in the Fn server output:

![extension in action](https://cdn-images-1.medium.com/max/1200/1*a39UiIt0-gCOtmAMS79y8Q.png)


Just like we implemented the Call listener, we could similarly add the App listener, middleware or custom API endpoints. Adding the App listener is similar to adding the Call listener — we’d need to create methods on our extension struct to satisfy the App listener interface, and then call AddAppListener function.

# Example: Cancel call middleware
Let’s show how would one implement a middleware function that checks if a certain header is present (`fn-cancel-call`) and cancels the chain of calls — that is, it doesn’t execute the function.

There are two different ways to inject custom middleware. One is using the `AddAPIMiddleware` — this function injects the middleware to all API endpoints such as:

```
/v1/apps
/v1/apps/:app
/v1/apps/:app/routes
...
```

The other function — `AddRootMiddleware` — injects the middleware to both API and your app calls as well.

To create a custom middleware we need implement a `Handle(next http.Handler) http.Handler` function on our extension struct. Just like before, the source code for the extension is available on [GitHub](https://github.com/peterj/fn-extensions).

The extension registration and setup part is the same as previously, the only difference is the implementation of the middleware and the fact that we call `AddRootMiddleware` function, instead of a `AddCallListener` function:

{% gist
https://gist.github.com/peterj/96585f8048b5d1cdcfb39d12712af049
%}

The logic for the middleware is in lines 30–40. We get the header named `fn-cancel-call` and if the value of that header is set to 1, we output a message and return from the function, canceling the remaining chain of middlewares. If cancel header is not set, we call the next handler in line for execution (`next.ServeHTTP`) and continue the execution.

# Example: Call logs using custom API endpoint
In this last example, we are going to implement a custom API app endpoint `/v1/apps/:app/logs` that connect to the Fn server database an returns a list of calls that were made to the app. We are going to return a couple of fields from that array to the user.

{% gist
https://gist.github.com/peterj/f5362ea4d861f1d80219ea2efe92e44a
%}

If you went through other examples, then the above code should look familiar. There are only a couple of differences — on line 23 where we are setting up the extension, we add the `Datastore` reference to our extension struct, so we can use it later in the `ServeHTTP` func and get the information about the calls. We also call the `AddAppEndpoint` to set up our custom API endpoint on the `/logs` path and specify the `GET` HTTP method.

The functionality of the extension is in the `ServerHTTP` func on line 35. Here, we set up a `CallFilter` first, then pass it to the `GetCalls` func on the datastore to retrieve the calls made to the app.

On line 43 we are using a func that’s coming from the Fn server package to send the error response, in case we can’t retrieve the calls.

Once we get the calls, we go through each one of them and write the call ID, status, path and time call started to the response writer.

Rebuild and run the Fn server then make the call to e.g. `localhost:8080/v1/apps/myapp/logs` — you will get an output similar to the one in the figure below (assuming you made some calls to that app).

![sample response](https://cdn-images-1.medium.com/max/800/1*-9Ux5ZLlznpHM41bcd3kdg.png)

# Conclusion
This article should serve you as a good introduction and getting started document for the Fn. It gives you the basics you need to start playing the serverless on your local machine and gets you thinking about different ways you can extend it.

I will probably write a follow up article where I’ll talk about [Fn Flow Server](https://github.com/fnproject/flow), [Fn Loadbalancer](https://github.com/fnproject/lb) and how to get Fn running on Kubernetes.

# Thanks for Reading!
Any feedback on this article is more than welcome! You can also follow me on [Twitter](https://twitter.com/pjausovec) and [GitHub](https://github.com/peterj). If you liked this and want to get notified when I write more stuff, you should subscribe to my [newsletter](https://tinyletter.com/pjausovec)!