Software Engineering

Combining FaaS functions using fn Flow to solve complex tasks

In the first two posts of this series I promoted FaaS as valuable architectural style for certain use cases and how to develop functions using Oracle’s fn Project. So far I combined several functions either using the unix shell or by manually executing several API calls. Of course this is not a solution for productive software. Therefore, this post will concentrate on fn Project’s approach of combining FaaS functions using fn Flow to solve complex tasks.

Combining functions with fn Flow

fn Flow

Besides fn’s main project several additional projects exist. One of those projects is fn Flow. Its goal is to provide a code centric state engine which can be used to combine several fn functions to a single application. Under the hood it can be compared to Amazon’s Step Functions. In contrast to AWS, fn Flow does not provide a visual builder. This design decision was made to provide developers a seamless coding experience. Functions are written in code, so why should the glue between them be applied in any different way?

In order to use fn Flow an additional fn Flow server is required. It can be started as documented in the project’s GitHub repository:

$> FNSERVER_IP=$(docker inspect --type container -f '{{.NetworkSettings.IPAddress}}' fnserver)
$> docker run --rm -d \
      -p 8081:8081 \
      -e API_URL="http://$FNSERVER_IP:8080/invoke" \
      -e no_proxy=$FNSERVER_IP \
      --name flowserver \

The first line retrieves the fn server’s IP address within the Docker network and sets the environment variable FNSERVER_IP. The second line creates the fn Flow server container. FNSERVER_IP is provided to the container such that fn Flow server can communicate with it.

In addition an experimental Flow UI exists which visualises running Fn Flows in real time. It can be started in a similar way:

$> export DOCKER_LOCALHOST=$(docker inspect --type container -f '{{.NetworkSettings.Gateway}}' functions)
$> docker run -p3000:3000 -e API_URL=http://$DOCKER_LOCALHOST:8080 -e COMPLETER_BASE_URL=http://$DOCKER_LOCALHOST:8081 fnproject/flow:ui

Now that we are up and running it is time to start writing our first flow. So far it is only possible to write fn Flow functions using Java. Additional languages should be supported in the future. However, the project and Oracle currently focus to provide a stable fn Project version to launch Oracle’s own FaaS Cloud Platform. Therefore I can not predict when and how fn Flows development is taken to the next level.

Nevertheless, let us start to develop our first flow function. I use Maven as my preferred build tool. For the project POM the following configurations are relevant:


An example POM can be found in my GitHub Repository. As with fn functions a func.yaml is required to provide some metadata. A basic example could look like this:

schema_version: 20180708
name: twitter-flow
version: 0.0.2
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.esentri.twitter.flow.TwitterFlow::handleTweets
format: http-stream
- name: twitterflow
  type: http
  source: /twitterflow

Next we will focus on writing a flow function. The full example can be found here. fn Flow’s approach to define flows is based on so called FlowFutures and a fluent API. In our example our flow processes a set of Twitter tweets scraped by a simple Node.js bot. For each tweet we want to send out a response. As the Twitter API is quite gossipy, first thing to do is strip down all twitter objects. For this we implemented another fn function. To call the function we create a FlowFuture and provide the expected function return type. This way the JSON payload is automatically transformed to a Java object:

Flow f = Flows.currentFlow();
FlowFuture<Tweets> answerTweetsFuture = f.invokeFunction(functionIds.get(TwitterFlowFunction.TWITTER_SHORTENER), tweetSearchResult, Tweets.class);

Now that we defined our FlowFuture we need to start the function call. The fn Flow API provides several methods which can be used in different scenarios. As we want to keep on processing the Tweets future result, we use the .thenAccept method. It returns another FlowFuture that can be used for further processing. Using Java functional programming semantics a processing branch might look like this:

answerTweetsFuture.thenAccept(tweets -> {
	LOGGER.debug("Tweet Count: {}", tweets.getTweets().size());
	tweets.getTweets().forEach(tweet -> {
		TweetAnswerPayload answerPayload = new TweetAnswerPayload(tweet.getUserScreenName(),
			"Danke für die Unterstützung des Vortrags! ;-)", tweet.getIdStr());

			FlowFuture<FunctionReturnValue> tweetFuture = f.invokeFunction(
				functionIds.get(TwitterFlowFunction.ANSWER_TWEET), answerPayload, FunctionReturnValue.class);

			tweetFuture.thenAccept(functionResult -> {
				LOGGER.debug("Tweet answered: {}, {}", tweet.getIdStr(), functionResult.getMessage());
}); -> {
	UserFilterPayload payload = new UserFilterPayload();

	payload.setUsers(new ArrayList<User>());
	tweetSearchResult.getStatuses().forEach(status -> {


	return payload;
	}).thenAccept(userFilter -> {
		f.invokeFunction(functionIds.get(TwitterFlowFunction.TWITTER_FILTER_FOLLOW), userFilter,
				.thenAccept(userList -> userList.getUsers().forEach(user -> {

As one can see the first function call result is returned as tweets and further processed. For each tweet in the returned object (tweets.getTweets().forEach(tweet ->)  a new FlowFuture calling our „Answer Tweet“ function is created. This FlowFuture is then fired and its output is logged.

Within this example it is important to note that we use an asynchronous development method. Although our flow function schedules all other function calls, it is not required to run throughout the whole processing. On the other hand answer tweet functions are processed in parallel leveraging fn Project’s scalability. It is even possible to start several processing branches in parallel (see example on GitHub).

My Conclusion regarding fn Flow

As mentioned in my previous posts, I like Oracle’s fn Project and what they are doing for the community in order to provide FaaS programming paradigms without vendor login. Unfortunately, I am not so fond of fn Flow and its development. First of all, I think the project team was able to create a solid base at the beginning of their project. Unfortunately, fn Flow and several other minor projects got pushed aside by prioritising fn core features higher. I totally understand the project’s and Oracle’s decision, however it results in a rather unstable fn Flow project with an unclear future.

Secondly, I am not convinced by the design decision that flows should be defined in code and nothing else. I can understand that developers prefer this approach for rather complicated function choreography, but it should be possible to define your simple „daily business“ flow using some visual editor and notation. If it gets more complicated one could still get back to code level. What I would prefer to see is something like this:

fn Flow example as BPMN 2.0 Model

This model exactly describes what is achieved within the flow code. However, you understand it at a glance. My question here is: Why not use established and well known standards? Oracle could even provide Oracle ICS and Process Cloud integration. In the end BPM engines are not more than a state engine such as fn Flow aims to be, aren’t they?