This article is originaly published on 208-12-02 on Java Advent Calender at https://www.javaadvent.com/2018/12/serverless-java-and-fn-project-first-steps.html
Serverless isn't a new thing, but it is fair to say there is still a lot of hype about it and how it will change everything, and how in the future everything will be serverless. Beside serverless/functions provided by cloud providers there are more and more serverless projects coming our way which goal is to break us from vendor lock-in and allow us to run serverless even on premise. Let us look at one such project FN Project.
If we go to the official website of FN project http://fnproject.io/ we can read something like this:
"The Fn project is an open-source container-native serverless platform that you can run anywhere - any cloud or on-premise. It's easy to use, supports every programming language, and is extensible and performant."
FN Project is an open source project backed by Oracle, which base functions on containers. So, in theory, anything that can become container and can read and write from/to stdin/stdout, can become a function in FN project. This is a very nice feature, since it means, that in theory, it can support any programing language, unlike serverless/functions provided by cloud providers, where if your language of choice wasn't supported you couldn't use it with serverless.
Another nice feature of FN Project is that it can run on-premise, or in the cloud, or multiple clouds or in the combination of all mentioned.
The only prerequisite for FN project is Docker 17.10.0-ce or later.
To setup FN project, we need only to download FN binary
and add it to the path. After this, we are ready to start playing with FN.
The first thing that we need to do is to start FN server. In order to do so, we only need to type this in a terminal/console
$ fn start
To validate that all is working good we can run this command
$ fn version
This will print version of fn server and fn client running on the machine. In the case of my laptop I get this values
$ fn version
Client version: 0.5.15
Server version: 0.3.595
Once we validated that all is good we can start to create our first function.
As mentioned FN project is "language agnostic", in theory, it can support any language, but it doesn't mean that it supports all languages at the moment. To see which languages are supported with the version we have we can run next command:
$ fn init --help
There is option -runtime which will list all options available on our machine. In my case, I will choose Java programing language. So to create the first function in Java we just need to run this command:
$ fn init --runtime java --trigger http function1
function1 is the name of the function, and here we put the name that we want to use. Option -trigger http means that we want to create HTTP trigger for our function which will allow us to call it over HTTP, for example via curl. After running this command fn will generate initial function for us and put it in the directory named as we named our function, in my case function1.
Let us look at what is generated
$ cd function1
$ find .
./src/main/java/com/example/fn/HelloFunction.java
./src/test/java/com/example/fn/HelloFunctionTest.java
./pom.xml
./func.yaml
If we open pom.xml file, it will look like any other pom.xml file. Only dependencies there for FN project will be dependencies for testing part, there are no dependencies for building or running our java fn function.
If we open HelloFunction.java, we will again see that it is plain Java class, with ZERO dependencies.
package com.example.fn;
public class HelloFunction {
public String handleRequest(String input) {
String name = (input == null || input.isEmpty()) ?
"world" : input;
return "Hello, " + name + "!";
}
}
There is only one method handleRequest that takes String as an input and provide String as an output. This is very different from writing functions in an implementation of cloud providers since they always add specific libraries or other types of dependencies in order for functions to work with their system. In case of FN since there are no dependencies it can run without any problem anywhere and you are not looked in into anything.
So how does then FN works? How it knows how to run our function?
All magic is in func.yaml file. Or to be more precise all configuration needed to create a function in FN project. Let us take a closer look at it.
$ cat func.yaml
schema_version: 20180708
name: function1
version: 0.0.1
runtime: java
build_image: fnproject/fn-java-fdk-build:jdk9-1.0.75
run_image: fnproject/fn-java-fdk:jdk9-1.0.75
cmd: com.example.fn.HelloFunction::handleRequest
format: http-stream
triggers:
- name: function1-trigger
type: http
source: /function1-trigger
There are multiple fields here:
Maybe you noticed that one of the generated files is HelloFunctionTest.java, this file is indeed unit test file for our function, which is also autogenerated for us, and populated with a simple example of the unit test. Let us take a look at that file.
public class HelloFunctionTest {
@Rule
public final FnTestingRule testing =
FnTestingRule.createDefault();
@Test
public void shouldReturnGreeting() {
testing.givenEvent().enqueue();
testing.thenRun(HelloFunction.class, "handleRequest");
FnResult result = testing.getOnlyResult();
assertEquals("Hello, world!",
result.getBodyAsString());
}
}
Except for some fn dependencies and part with @Rule, everything else looks like any other JUnit test in java. This unit test will just invoke our function without passing any parameters, and check if a result is "Hello world!". The great thing about this test is that we can run it like any other unit test, we can invoke it from maven or IDE in any standard way.
Let us now write the test where we will pass some arguments and validated that our function still works as expected. In order to do so, we can add this code to our test class
@Test
public void shouldReturnGreetingwithBodyValue() {
testing.givenEvent().withBody("Java").enqueue();
testing.thenRun(HelloFunction.class, "handleRequest");
FnResult result = testing.getOnlyResult();
assertEquals("Hello, Java!",
result.getBodyAsString());
}
Again, we can run it like any other unit test and validate that all is good.
Now that we defined our function, we understand what files are generated and what is their purpose, we also did unit testing. Then it is time for us to deploy and invoke the function. We can deploy our function to the cloud and docker registry, but it is much easier and faster to deploy it only locally especially while we are busy in development. To deploy function we just need to run this command
$ fn deploy --app myapp1 --local
Here we are telling fn to deploy our function into application myapp1, and to deploy it only locally by providing option -local. Once we successfully deployed our function, we can invoke it. To invoke it we can run next command
$ fn invoke myapp1 function1
We provide the name of our application and the name of our function. If we would like to provide input to our function we can do it in this way
$ echo "Java is great" | fn invoke myapp1 function1
If you remember we also created HTTP trigger, so let's use it to invoke our function.
$ curl http://localhost:8080/t/myapp1/function1-trigger
We can already do a lot of nice things with this, but let us move to the next level, where we will use JSON as input and output of our FN functions. First, we need to create a simple POJO class, something like this
public class Hello {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
now we can modify our function to take this class as input and output, so the function would look like this
public Hello handleRequest(Hello input) {
String name = (input == null ||
input.getMessage().isEmpty()) ? "world" :
input.getMessage();
Hello hello = new Hello();
hello.setMessage(message + ", " + name + "!")
return hello;
}
after we deploy function we can invoke it like this
$ curl -d '{"message":"JSON Input"}' \
http://localhost:8080/t/myapp1/function1-trigger
As we saw starting to develop functions with FN project is very easy and fun, also in the small amount of time we can create powerful functions.
What we saw here is only part of possibilities of FN Project, for more info about FN in general and more info about possibilities I would suggest looking at websites listed below