Skip to Content
Technical Articles

Writing Function-as-a-Service [2]: Local Development

With other words

How to write serverless function
for
SAP Cloud Platform Extension Center, serverless runtime
on local laptop
using
Command Line Client

This blog is part of a series of tutorials explaining how to write serverless functions using the Functions-as-a-Service offering in SAP Cloud Platform Extension Factory, serverless runtime

Quicklinks:
Sample Code
Commands

Introduction

To develop serverless functions, based on SAP Cloud Platform Extension Factory, serverless runtime, it is not mandatory to use the browser tool Extension Factory.
You can develop functions in Node.js locally, using your favorite development environment, etc

Overview

1. Designtime
Create Project, Function and Trigger
2. Runtime
Deploy, use the function, view Logs
3. Appendix
Commands, Alternatives, Extension Center

Prerequisites

– Access to SAP Cloud Platform, productive account. Trial isn’t supported
– Instance of Extension Factory, serverless runtime (see Preparation blog)
– Cloud Foundry CLI installed locally (see same Preparation blog)
– Functions CLI available locally (see still same link)
Node.js installed

1. Designtime

Use your preferred development environment for Node.js, e.g. this one

1.1. Create Project

We have to manually create the folder structure:
We create a root folder for our locally developed FaaS project:
C:\tmp_faaslocal
This name is only a folder name in file system. It doesn’t have impact on the name of the FaaS  project which we’re going to deploy
In the root folder, we create:

faas.json

This is the manifest of the FaaS project. The name is fix and cannot be changed

package.json
The name of the descriptor of Node.js applications is fix as well

mylocalmodule.js
A javascript file containing our implementation

The project structure should look like this

No doubt, it is very simple
But we’ll see something even more simple below

package.json

Our Function-as-a-Service project is realized as executable Node.js module and the package.json descriptor file is required by the Node.js runtime.
Copy the following content into your package.json file

{}

No doubt, it is very simple
Note:
No, it isn’t a joke

faas.json

This is the main entry point of a function project. Reading this file is the first task of the command line tool
So this file must be located in the root folder of a functions project

Copy the following content into the file

{
	"project": "localproject",
	"version": "1",
	"runtime": "nodejs10",
	"library": ".",
	"functions": {
		"mylocalfunction": {
			"module": "mylocalmodule.js",
			"handler": "mylocalhandler"
		}
	},
	"triggers": {
		"mylocalhttptrigger": {
			"type": "HTTP",
			"function": "mylocalfunction"
		}
	}
}

We can see that a project manifest has several mandatory properties

project
As usual, everything has a name, so even a project has a name
And as usual, I’m giving silly names. That makes clear which names are fix and which names can be freely chosen by us.
BTW, I have to correct myself: there’s one exception: the manifest file has a silly name which wasn’t chosen freely by me…

version
Obviously, the version, blabla

runtime
Make sure to use only one of the supported runtimes
Which are the supported runtimes?
Currently: nodejs8 and nodejs10
How to know the current supported runtimes?
One thing for sure: my blog might not be always uptodate
So how to check?
Some suggestions:
Run project check on command line, or try to deploy, or check the wizard in Extension Center
What about docu?
Yes

library
Our first project is very simple, so we don’t need a library folder.
We just point to current directory
Normally, we would enter here the relative path to a folder containing  the required javascript files

functions
Interesting: the definition of a function
First of all, there can be multiple functions in a project
Each function is identified by its name
BTW, function names do have restrictions, e.g. no uppercase, no special characters (remember the very “special” character of cats…)

module
We have enter the javascript filename, which is not only a silly javascript file, but at the same time it is a module in terms of Node.js
And even one more declaration is required:

handler
This is the name of a function in the javascript file. This function is the main entry point of the implementation
It is the code which is initially triggered by the FaaS runtime
This handler has to be exported such that it can be invoked by the runtime

triggers
There can be multiple triggers in a project
Each trigger is identified by its name

function
Each trigger is an independent artifact in terms of FaaS project
Therefore, a trigger has to point to a function

type
The value is a hardcoded type, I mean: here we cannot write any silly name
Supported types are: HTTP, Timer, AMQP, CloudEvents
I recommend the serious docu page to learn which types are supported
Depending on the chosen type, there are more settings required in faas.json

1.2. Create Function

Actually, we’ve already defined the function above, in the faas.json.
Now we have to stick to the file name and also the handler name

mylocalmodule.js

Finally, this javascript file contains the actual implementation of the function in the Function-as-a-Service project (as you know, the “project” is also known as “Extension”)
This file contains the code
There can be more javascript files
But not today

Requirements:
The javascript file must contain javascript code
It must be a Node.js module which exports a function (the handler)
A soft requirement:
A function should be rather silly and simple and small (as I call it in my language. Please check out Pradeep Pandas blog: THAT language is really noble and exquisite)

For today, just copy the following code:

 module.exports = { 
	mylocalhandler: function (event, context) { 
		const s=' ',s2=s+s,s3=s2+s,h='-',s4=s2+s2,c=':',s5=s4+s,k=',',s1=s5+s5,v='|',b='\\',f='/',o='(',e=')',u='_',u2=u+u,d='.',u3=u2+u,i="'",u5=u3+u2,p=';',q='\"',t='`',z='^',r=s+'\n';	
		console.log(''+h+h+s+'miaow'+s+h+h+r) ;
        return ""+r+s1+s1+v+b+s+f+b+r+s4+u2+s1+s4+v+k+b+o+u+b+u+r+s3+o+s+o+s1+s4+v+b+k+t+s3+t+h+z+d+r+s4+b+s+b+s1+s3+c+s4+t+h+i+s3+e+r+s5+b+s+b+s1+s3+b+s5+s3+p+r+s5+s+b+s+b+s1+s3+t+h+d+s3+k+i+r+s5+s2+b+s+b+s+u5+u5+u2+k+i+s2+o+r+s5+s3+p+s+i+s1+s5+s+p+r+s5+s3+b+s5+s5+s5+s2+f+u3+k+h+d+r+s5+s4+t+k+s4+k+u5+v+s2+p+i+u5+k+i+r+s5+s2+k+h+q+s+b+s2+c+s5+s+v+s+c+r+s5+s+o+s+d+h+q+s+b+s+t+d+u2+s3+v+s+v+r+s5+s2+b+u2+e+s2+t+d+u2+k+i+s2+v+u2+e+s2+'SSt';
	  } 
 }

It follows my requirement:
It is silly

Nevertheless, we can see:
We define a function
The name of that function corresponds to the handler property which we declared in our faas.json file
At the same time, it is exported in terms of Node.js module

 

The function itself does something:
In our silly example, it just writes anything to the log
And the function has a return value
As such, when invoked via HTTP, we can see the return value as HTTP response in the browser

It doesn’t do anything else. It doesn’t take any benefit of the powerful Functions – API
No it doesn’t.
But nevertheless, it is nice
Hum…really…?
You should give it a try
Just to see the response

Summarizing:

A minimal Function project consists of
1 javascript file as module, with one exported handler-method
1 manifest file called faas.json, containing declaration of function and trigger
1 package.json file in case of Node.js runtime

1.3. Create Trigger

This is a silly chapter.
Because we’ve already defined the trigger in the faas.json
There’s nothing else to create

However, just a note:
We’ve defined an HTTP trigger
But how does that help us?
HTTP trigger means that any person can call (trigger) the function via HTTP
But also a tool can do that programmatically
Even an animal can do it (if it doesn’t sleep)

All of them need one thing:
The URL
And patience
The URL is created during deployment by the framework and can be seen afterwards in the details or in the log output

Note:
Without a trigger, the function is useless
It’s like a cat in coma
The faCLI will refuse to deploy if no trigger is defined

2. Runtime

After we’ve created the required files locally, it is time to talk about the actual interesting topic of this blog:
How do we communicate with FaaS runtime (XFsr instance)?
How is our node-module being converted into a serverless function?

The answer is:
With the help of the command line tool
The following examples assume that you’ve prepared the CLI tool for FaaS and it is added to the PATH variable

2.1. Deploy

So now’s time to deploy our FaaS project to the FaaS runtime

As mentioned before (I don’t remember if I wrote it in a blog – or if I mentioned it while talking to a friend), the SAP Cloud Platform Extension Factory, serverless runtime offers a command line tool to interact with the Functions runtime:
The SAP Cloud Platform Extension Factory, serverless runtime CLI

In my language, I call it FaaS CLI.
Is it OK?
This tool has a prerequisite:
The Cloud Foundry Command Line Interface (I call it cf CLI)

In my language:
fa CLI requires cf CLI

I mentioned that before, I know for sure, info can be found here

OK, the faCLI interacts with a service instance on the SAP Cloud Platform.
As such, for communication with the Cloud Foundry environment of SAP Cloud Platform it uses the cfCLI internally

Let’s login with the cfCLI

cf login

It asks us to enter all the user credential and org stuff

Once we’re in the space where we created the service instance of Extension Factory, serverless runtime, we leave the cfCLI alone

We navigate to our folder where we created the FaaS project

cd C:\tmp_faaslocal

Now we use the faCLI to login to the FaaS runtime

xfsrt-cli login

The command prints the Cloud Foundry details where we just logged on.
It is useful, to make sure that we’re not deploying to wrong target space
Then it proposes the service instances found in that space
We don’t need to type any instance name, just enter the number
Convenient!
And even more:
If there’s only 1 instance, then we can just press Enter, no need to type the 1
Even more convenient!
The faCLI knows that we’re lazy (and reading this blog makes sleepy)

The deploy command is easy as well:

xfsrt-cli faas project deploy

Note:
The faCLI needs to know which project to deploy.
In our example, we’ve stepped into the project folder, so the faCLI just deploys that project
Convenient!
In other cases, the path to the project has to be entered via command parameter -p

The result of the deploy command:
potential error message, generic info and:
the info which we were waiting for.
Which info?
Sorry if I talk too much and you already forgot that you were waiting for an info
Sometimes I talk so much to my cat (silly conversations): so it forgets that it has caught a mouse – then I can liberate the mouse from such embarrassing situation (being caught by a sleepy pussycat)

OK, see below what we were waiting for:

Yes, we were waiting for the URL of our function
Now we have it
So now we can call (trigger) our function

2.2. Use the Function

In a browser window, we invoke that URL.
The trigger is activated
The function wakes up and does something (in the log)
The function returns something, it is visible in the browser:

Is this the return value?
No, the response is locked
Come on…
No no, you have to try it yourself

2.3. View the log

To view the entry which we’ve written to the log, we can again use the faCLI

xfsrt-cli faas project logs

Again, a nice command and easy to remember.
Just make sure to jump into the project folder, same folder where the faas.json is located
As a result, we can see that the function has done its job:

2.4. View the log again

Let’s play little bit with the CAT sorry, the CLI

Make sure to trigger the function multiple times in the browser, to have some amount of log entries to look at

Try adding the param -t to the command:

xfsrt-cli faas project logs -t

It displays the timestamp of each log.
Sometimes, it is necessary to know, when a log was written, but mostly it is not required and disturbs.
Alternatively, for those who like typing

xfsrt-cli faas project logs –timestamps

I like this command, because I never use it….

Try another option:

xfsrt-cli faas project logs –tail 10

One of the possibilities to reduce the mostly crowded log entries
I like this flag and it’s easy to remember, like pulling the tail of a cat
With other words: tail means, you see just the remaining tail of the cat

medium%20tail

The following log command mentions explicitly the project name and adds a filter to view only logs produced by the given function

xfsrt-cli faas project logs localproject –functions mylocalfunction

Finally, checkout more options with the help:

xfsrt-cli faas project logs -h

Summary

In this blog we’ve learned how to efficiently work on local machine instead of using browser based tool.
We deploy the project to the FaaS runtime (not to Cloud Foundry)
Basically, we only need to download the Command Line Client for  FaaS

Links

Download SAP Cloud Platform Extension Factory, serverless runtime CLI from https://tools.hana.ondemand.com/#cloud
Prerequisite: Cloud Foundry CLI
Link collection

Appendix 1: Useful Commands

Other than your cat, the CLI will always do what you want..

xfsrt-cli login  requires login to CF

xfsrt-cli faas project deploy  executed from project root folder

xfsrt-cli faas project get  prints info, e.g. URL for HTTP trigger

xfsrt-cli faas project check  runs some checks and prints errors, warnings, etc

xfsrt-cli faas project logs to view the logs

add -v to make the CLI more verbose

Appendix 2: Alternative Implementations

library folder

As mentioned, it is recommended to use a separate subfolder for the implementation file(s)
In that case, the property library in faas.json needs to be adapted accordingly
And the project structure would look like this:

shortcut for handler

If your function contains only one method, you can export without giving a name

module.exports = function(event, context) {
   . . . 
);

In that case, remove the “handler” property from your faas.json

Appendix 3: Extension Center

If you have access to Extension Center (see preparation), you can see that our deployed project appears there as an extension

Appendix 4: All Sample Project Files

In our example, all files are located in the same (root) directory

faas.json

{
	"project": "localproject",
	"version": "1",
	"runtime": "nodejs10",
	"library": ".",
	"functions": {
		"mylocalfunction": {
			"module": "mylocalmodule.js",
			"handler": "mylocalhandler"
		}
	},
	"triggers": {
		"mylocalhttptrigger": {
			"type": "HTTP",
			"function": "mylocalfunction"
		}
	}
}

package.json

{}

mylocalmodule.js

 module.exports = { 
	mylocalhandler: function (event, context) { 
		const s=' ',s2=s+s,s3=s2+s,h='-',s4=s2+s2,c=':',s5=s4+s,k=',',s1=s5+s5,v='|',b='\\',f='/',o='(',e=')',u='_',u2=u+u,d='.',u3=u2+u,i="'",u5=u3+u2,p=';',q='\"',t='`',z='^',r=s+'\n';				console.log(''+h+h+s+'miaow'+s+h+h+r) ;
        return ""+r+s1+s1+v+b+s+f+b+r+s4+u2+s1+s4+v+k+b+o+u+b+u+r+s3+o+s+o+s1+s4+v+b+k+t+s3+t+h+z+d+r+s4+b+s+b+s1+s3+c+s4+t+h+i+s3+e+r+s5+b+s+b+s1+s3+b+s5+s3+p+r+s5+s+b+s+b+s1+s3+t+h+d+s3+k+i+r+s5+s2+b+s+b+s+u5+u5+u2+k+i+s2+o+r+s5+s3+p+s+i+s1+s5+s+p+r+s5+s3+b+s5+s5+s5+s2+f+u3+k+h+d+r+s5+s4+t+k+s4+k+u5+v+s2+p+i+u5+k+i+r+s5+s2+k+h+q+s+b+s2+c+s5+s+v+s+c+r+s5+s+o+s+d+h+q+s+b+s+t+d+u2+s3+v+s+v+r+s5+s2+b+u2+e+s2+t+d+u2+k+i+s2+v+u2+e+s2+'SSt';
	  } 
 }

 

3 Comments
You must be Logged on to comment or reply to a post.
  • Awesome blog!

    Really good introduction, thank you so much for sharing.

    Any chance you try developing with SAP Business Application Studio?

    • Hello Neo Kung ,
      Thank you very much for the nice feedback !
      Are you asking because you’ve read in the docu that there’s a project template in SAP Business Application Studio?
      Well, I’ve heard that my good friend Pradeep Panda is preparing a blog covering that topic.
      It will be nice to read, so beautiful and colorful English…
      I will post the link once it is available

      Cheers,
      Carlos

      • Hello Carlos,

        Yes, as I’m using BAS on a daily basis now and the plugin for FaaS recently available.
        So definitely looking forward to the blog you mentioned.

         

        Thanks!