Serverless Functions

This section discusses how to implement business behavior using serverless functions.

What is a Trillo Serverless Function?

Trillo serverless function is a modular code performing a specific functionality that runs inside a Trillo microservice with access to its environment within a security context. It works as if the code was linked at the time of packaging the service but it is supplied as a modular piece of code at runtime.
The serverless function lets you extend the Trillo microservice functionality dynamically, without having to worry about DevOps, CI/CD.
A serverless function is automatically published as an API. It can be called using the POST method and can be passed parameter as the post-body. The body becomes available to the function as a parameter (as a map of key-value).
We have used serverless functions for complex applications. Our experience is that it works without any noticeable performance penalty. It makes the development of the application modular - a significant goal of the microservice architecture.

Create a Function

Creating a serverless function using Trillo Workbench is simple. Simply select Functions menu options in the left navigation. Select New Function on the top in the middle column.
Currently, Trillo Workbench supports Java and MVEL languages. Select Java as the option.
Say, you create a function by giving it a name as MyFunction. The function will be created with the following stubbed code.
import com.collager.trillo.pojo.Result;
import com.collager.trillo.pojo.ScriptParameter;
import com.collager.trillo.util.Loggable;
import com.collager.trillo.util.TrilloFunction;
import java.util.Arrays;
import java.util.Map;
/*
- Everything is private except handle() method
- return any object and it will become the response payload
- Make sure that returned object is serializable.
*/
public class MyFunction implements Loggable, TrilloFunction {
/*
This is the only entry point into this Trillo function
- params : complete context passed by the application
including request body map (V).
*/
public Object handle(ScriptParameter params) {
try {
return _handle(params);
} catch (Exception e) {
log().warn(Arrays.toString(e.getStackTrace()));
return Result.getFailedResult(e.getMessage());
}
}
private Object _handle(ScriptParameter params) {
// insert the correct biz logic
return params.getV();
}
}
Ignore import on the top of the file for now. We will provide a reference to them later in the document.

Methods of Function

Each serverless function starts with the following two methods.
  • handle: This is the entry point of the function that is invoked by the runtime by passing ScriptParameter (see below).
  • _handle: A private method that is invoked by the method handle.
  • handle method catches all exception and unwraps all error messages and pass as Result object.

ScriptParameter

ScriptParameter type has a special property returned by getV method call. This is an object that represents the parameters passed to the function, for example, in the post-body. It is a java map and can be cast as follows:
Map<String, Object> map = (Map<String, Object>) params.getV();
In addition to function parameters, the ScriptParameter has accessor methods for the following properties:
Parameter Name
Description
isOfUser
Internal identifier of the current user (on whose behalf the call is made, it may be a system user).
userId
User id (assigned to the user when it was created, also login userId)
firstName
First name of the user
lastName
Last name of the user.
email
Email of the user.
role
Roles assigned to the user.
emailVerified
A flag indicating if the user's email has been verified or not.
tenantId
If the deployment is a multi-tenant then it returns the id of the tenant.
tenantName
If the deployment is a multi-tenant then it returns the name of the tenant.
executionId
If the function is running as a background job then it gives the identifier which is used to track the task in the database (it is internal is you can ignore it barring an advanced use-case).
taskName
If the function is running as a background job then it gives the name of the task. It may be useful for logging.
stateMap
An arbitrary java.util.map can be used to store some state information. This is useful when a function calls another function. This map is passed back to the calling function with any updates made by the caller function. This is useful, for example, for building a lookup cache in a chain of function.
All Trillo serverless function runs within a transaction boundary. By default, the transaction is rolled back if the function invocation returns a Result object with an error. Since a function may run few steps that may be committed irrespective of the final result, Trillo SDK provides an API to commit the transaction. After a transaction is committed, a new transaction is created.

Sample Hello World Function

To learn more about the function, see the modified _handle method with this simple code:
private Object _handle(ScriptParameter params) {
// insert the correct biz logic
Map<String, Object> functionParameters = (Map<String, Object>)params.getV();
String hello = "Hello, " + params.getUserId();
if (functionParameters.containsKey("youSaid")) {
hello += "\n you said, " + functionParameters.get("youSaid");
}
return hello;
}
Notice that this method simply echos "Hello <current user id>". It adds the parameter "youSaid" if it is passed.

Test Function

The above function can be tested using Trillo WOrkbench as shown below:
Testing Function by Selecting Execute Tab, Enter Parameter and Click Execute

Result

Notice in the above figure, the actual result is wrapped inside an object and passed as its data attribute. The wrapper class is Result (a java class). It contains the actual result in the data attribute. It has other attributes for status, error message, detailed message, etc. A special attribute called _rtag is included with a special value _r_ to assist a JavaScript client in identifying if the returned value is an instance of Result.

Log Messages In a Function

The serverless function can use logging APIs available in Trillo SDK. See the modified example code below:
private Object _handle(ScriptParameter params) {
// insert the correct biz logic
Tapix.logInfo("Entered _handle()");
Map<String, Object> functionParameters = (Map<String, Object>)params.getV();
String hello = "Hello, " + params.getUserId();
if (functionParameters.containsKey("youSaid")) {
hello += "\n you said, " + functionParameters.get("youSaid");
}
Tapix.logInfo("Exiting _handle()");
return hello;
}

Running a Function as a Job

A function can be executed as a background job. This is useful for a long-running job where the function can orchestrate a process using other functions, scripts, and GCP services.
See the chapter on Long-Running Tasks for details.

Audit Logs

Audit logs are similar to the log messages discussed above with a distinction that audit log calls also log messages in the database. This is useful for auditing important business events. It is also useful to track, troubleshoot and monitor the status of long-running jobs.
The following code shows how to use logging vs audit logs.
Tapix.logInfo("Entered _handle()"); // this is a log
Tapix.auditLogInfo("Entered _handle()"); // this is audit log

Sample Function to Access DB

The following is an example of a function making a call to DB to retrieve a user from the user_tbl by email.
#TODO

Sample Function Calling Another Function

The following is an example of a function MyFunction calling MyFunction2.
#TODO

Trillo SDK (APIs) in a Function

Trillo SDK provides several APIs to simplify writing a serverless function code. These APIs include:
  • Database access
  • Google cloud service
  • External restful service
  • Creating long-running jobs
  • CVS, JSON file processing
  • ...
  • ...
  • many more
Refer to Trillo Serverless Function SDK (APIs) for a detailed list of all APIs.
Not only Trillo SDK functions, but several open-source libraries that are integrated with the Trillo runtime, become available for use in your serverless functions (such as Apache Commons for example).

What you achieved?

In this chapter, we covered how to add business logic using serverless functions, the structure of the function, a mention of APIs, logging, and a long-running job.

What is next?

In the following section, we will cover how to ingest data into database tables. This may be needed for the development and testing of the application. Or, it may be used for production. You may skip over it if the data ingestion is not required.