Ballerina Services in Serverless World

Running a Ballerina Service as a Serverless function in AWS Lambda

Isuru Perera
8 min readJan 10, 2018

Ballerina is a programming language optimized for integration and it is being developed by WSO2. With Ballerina, it’s very easy to write integration services. For example, see following Hello World Service in Ballerina (hello-world-service.bal).

import ballerina.net.http;service<http> helloWorld {
resource sayHello (http:Request req, http:Response res) {
res.setStringPayload("Hello, World!");
_ = res.send();
}
}

When you compile (ballerina build hello-world-service.bal) and run this program (ballerina run hello-world-service.balx), the Ballerina will run an HTTP server as it recognizes that you want to expose a service directly over the network via HTTP protocol. For example, you can now directly send a GET request to the Ballerina service resource named “sayHello”.

Example:

$ curl http://localhost:9090/helloWorld/sayHello
Hello, World!

There is much more you can do with Ballerina and you can go through the Ballerina By Example page to learn about the language features.

In this story, I’m explaining how you can run your Ballerina Service as a serverless function in AWS Lambda. Before that, let’s see what is meant by “Serverless”.

What is Serverless and AWS Lambda?

Serverless is simply about not having to worry about servers when you write an application and you only focus on the application logic. Basically you don’t need to manage your own server hosts and the server processes. This is just a simple explanation about “serverless”. I recommend you to read more about “Serverless Architectures”. O’Reilly also has a great free book with the title “What is Serverless?”.

There are many benefits in serverless world. Most importantly, it reduces costs and there is a shorter lead time. This is very important for innovation as you can vastly reduce the time to implement your application.

AWS Lambda is a compute service provided by Amazon Web Services. With AWS Lambda, you can deploy your code and run it as a serverless function. The AWS Lambda will manage the server resources and the server process for you. You only need to concentrate on your application logic.

Following are the languages supported by AWS Lambda currently.

  1. Java
  2. Node.js
  3. C#
  4. Python

Currently, the Ballerina runs on BVM (Ballerina Virtual Machine), which is a Java based VM. Therefore, we can use the Java Language Runtime provided by AWS Lambda.

How does AWS Lambda work?

In AWS Lambda, we deploy the code to run based on events. For example, when there is an HTTP request, the AWS Lambda can trigger your code.

In AWS Lambda, regardless of the language, we need to write a handler method, which is first called by AWS Lambda when it begins executing the code.

How to invoke Ballerina Service Resource?

Since Ballerina is running on the JVM, we need to write a handler in Java.

Within this handler method, we need to process the request and invoke a Ballerina service resource directly.

Running the Ballerina server within the handler is not an option as the serverless function has to be stateless and we cannot start a server for each HTTP request event. Fortunately, there is a way to directly execute a Ballerina service resource using Java APIs. These Java APIs are not official APIs and those may be removed in future.

This is similar to the approach used by Lambada framework, which allows you to implement JAX-RS APIs and deploy in a serverless fashion. With Lambada, we can migrate existing JAX-RS applications to AWS Lambda. Lambada framework basically invokes the JAX-RS method directly using the AWS Lambda handler method.

To implement the Handler, I used the Java interface com.amazonaws.services.lambda.runtime.RequestHandler and used POJO classes for request and response.

In the constructor, the Ballerina service is compiled using an API and the compiled result is kept in the memory. Currently there is no API to directly run a compiled Ballerina service (with .balx extension).

The handler method locates the service resource in the compiled Ballerina service and executes it programmatically.

Since we are planning to use an HTTP request to trigger the handler method, we can use the “Amazon API Gateway”. When using the API Gateway, the input and output formats are predefined. Therefore, we just have to develop the request and response classes mapping to the input and output formats.

I created a Maven project and implemented a request handler as mentioned above. The source code is available at: https://github.com/chrishantha/ballerina-lambda

How to run your own Ballerina service in AWS Lambda

With the “ballerina-lambda”, now you can run your own Ballerina service without having worrying about any AWS Lambda specific code.

It’s important to note that “ballerina-lambda” project is only supporting to invoke a specific service resource method in your Ballerina method.

Let’s take a sample Ballerina Service

package helloService;import ballerina.net.http;@http:configuration {basePath:"/hello"}
service<http> helloService {
@http:resourceConfig {
methods:["POST"],
path:"/"
}
resource sayHello(http:Request req, http:Response res) {
json jsonRequest = req.getJsonPayload();
string firstName;
string lastName;
firstName, _ = (string)jsonRequest.Name.FirstName;
lastName, _ = (string)jsonRequest.Name.LastName;
json payload = {"Response":""};
payload.Response = "Hello, " + firstName + " " + lastName;
res.setJsonPayload(payload);
_ = res.send();
}
}

This service accepts a JSON payload as follows.

{
"Name":{
"FirstName":"Isuru",
"LastName":"Perera"
}
}

Now, to deploy this Ballerina Service to the AWS Lambda, we need to create a deployment package.

Following are the steps to create the deployment package

git clone https://github.com/chrishantha/ballerina-lambda --depth=1
cd ballerina-lambda/
  • Save ballerina service in ballerina-services directory. Currently, the ballerina-lambda expects a ballerina package name, which includes the Ballerina service. Therefore, you need to make sure that the ballerina service is in a package. There is already a sample helloWorldService inside the ballerina-services directory.
cd ballerina-services/
mkdir helloService
cd helloService
vi helloService.bal #Save above helloService in this file.
cd ../../
  • Now build the maven project.
mvn clean package
  • There should be a zip file named ballerina-lambda-1.0.0-SNAPSHOT.zip inside target directory.
  • Now login to AWS Console and visit “Lambda” service.
  • Click on “Create function”. Let’s author a Lambda function from scratch.
  • Use BallerinaHelloService for the name and select Java 8 for the runtime.
  • You also need to select a role for the function. You can create a new role named “BallerinaHelloServiceRole” and select “Basic Edge Lambda permissions” from Policy templates. You may also select an existing role if you already have one.
  • Click on “Create function”. After that you need to configure the Lambda function.
  • In (1), you need to upload the ballerina-lambda-1.0.0-SNAPSHOT.zip file.
  • Then you need to specify the handler method in (2). The handler method for ballerina-lambda project is as follows.
com.github.chrishantha.lambda.ballerina.BallerinaRequestHandler::handleRequest
  • Add a trigger (3) using “API Gateway”.
  • When configuring, click on “Enter value” for API name (1) and specify “BallerinaHelloService”
  • Deployment stage (2) is “prod”
  • For “Security” (3), select “Open” and “Add” the trigger.
  • Click on “BallerinaHelloService” again to configure “Environment variables” and add following environment keys and values.
BAL_PACKAGE=helloService
BAL_SERVICE_PATH=/hello/
BAL_SERVICE_METHOD=POST
  • Click “Save” to save the “BallerinaHelloService” function.
  • Now you can click on “API Gateway” configuration to see the “Invoke URL” for the Lambda function.
  • You can send an HTTP request to the “Invoke URL” and test the Ballerina Service running as a Lambda function. For example:
curl -d '{"Name": {"FirstName": "Isuru", "LastName":"Perera"}}' https://sqwswbj72a.execute-api.us-east-1.amazonaws.com/prod/BallerinaHelloService
{"Response":"Hello, Isuru Perera"}

What happens when we trigger the Lambda function?

Since the Ballerina Hello Service Lambda function is working as a serverless function, the “function instance” will be created only when we send an HTTP request. For subsequent requests, the “function instance” may be reused and we don’t know how long will the “function instance” be alive.

See following sequence diagrams to understand how the first request and subsequent requests work.

Sequence Diagram for AWS Lambda First Request
Sequence Diagram for AWS Lambda Subsequent Requests

Above sequence diagrams are important to understand, especially when you are concerned about response time latencies.

The ballerina-lambda function code internally measures the times at different points and send those data via response headers. Following are the response headers:

  • VM Startup Time (ms) — This is the startup time of the JVM. This is only valid for the first request.
  • Handler Init Time (ms) — This is the time taken to initialize the request handler. This is basically the time taken for the request handler constructor, which includes the Ballerina service compilation. This is only valid for the first request.
  • Request Processing Time (ms) — This is the processing time of the request handling method.
  • Lambda Remaining Time (ms) — This is the remaining time of the Lambda function (as calculated by AWS Lambda). This is taken from AWS Context object. This is the remaining time from Lambda Timeout, which is configured in “Basic settings”. So, this roughly equals to “Lambda Timeout (ms) - Request Processing Time (ms)”
  • Lambda Up Time (ms) — This is the JVM UP time at the end of request processing.
  • Amazon Request ID — This is the request ID from Amazon.
  • Function Name — This is the name of the AWS Lambda Function
  • Function Version — This is the version of the AWS Lambda Function

The first request time includes “VM Startup Time” + “Handler Init Time” + “Request Processing Time” + Network Latency.

All subsequent requests only include “Request Processing Time” + Network Latency.

However, for all requests, AWS Lambda is charging only for “Request Processing Time” + Network Latency.

Update (Jan 18): To get an idea about the timing measurements in AWS Lambda, I compared a Ballerina Hello Service with a simple Java Hello World method. I executed the Lambda function 10 times using a JMeter script and measured how long it takes to serve the first request. I executed the JMeter multiple times and following is the summary. Since AWS Lambda will reuse function instances and we don’t know when AWS lambda will terminate the instances, I redeployed the function to measure the first request response time.

AWS Lambda Ballerina Service and Java Method Invocations via AWS Gateway — Results

Note: Above Ballerina results were taken with Ballerina v0.95.3 and v0.95.4 versions.

Conclusion

In this story, I explained how you can run your Ballerina Service as a Serverless function in AWS Lambda.

You don’t have to do any AWS Lambda specific development and you can directly use the ballerina-lambda project to deploy your own Ballerina Service in AWS Lambda.

Amazon API Gateway can be used to trigger the Lambda function. The first request response time includes the time to start the function instance and the time to initialize the handler in addition to the request processing time and network latency.

--

--