Serverless Framework is an excellent choice for writing and deploying Lambda Functions in AWS. Having written a blog series on using it with Java8+Maven, I’ve been asked if there is a way to not have too many handlers (One per API), and somehow provide multiple handler functions in the same java class. In this post I provide the structure that’d allow for multiple handlers using a single Java class.

This is particularly useful when the code is tightly coupled around a resource (CRUD) and writing a handler class for each of C, R, U, D seems un-necessary, like it should.

  • Make sure you’ve Serverless installed, and the AWS CLI is configured with the necessary policies.
bash-3.2$ serverless -v
1.26.0

Please ensure your serverless is at least 1.26.0 - There was a fairly hacky way to do this before this pull request.

  • Create a boilerplate serverless AWS Java8 Maven project.
bash-3.2$ mkdir multiple-handlers
bash-3.2$ cd multiple-handlers/
bash-3.2$ serverless create --template aws-java-maven
Serverless: Generating boilerplate...
Serverless: Successfully generated boilerplate for template: "aws-java-maven"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name
bash-3.2$ tree
.
├── pom.xml
├── serverless.yml
└── src
    └── main
        ├── java
        │   └── com
        │       └── serverless
        │           ├── ApiGatewayResponse.java
        │           ├── Handler.java
        │           └── Response.java
        └── resources
            └── log4j.properties

6 directories, 6 files
  • Check the serverless.yml, if you ignore all the comments, this is what it looks like -
service: aws-java-maven # NOTE: update this with your service name

provider:
  name: aws
  runtime: java8

# you can add packaging information here
package:
  artifact: target/hello-dev.jar

functions:
  hello:
    handler: com.serverless.Handler

The package section is what tells serverless to locate the artifacts.

  • Let’s add another function to the auto-generated Handler class, which already has one function defined called handleRequest which maps to the lambda function hello, as noticed in the serverless.yml above.

We will add a new method called handleAnotherRequest in the same class. To differentiate the output, we’ll change the Response to output a different string than the auto-generated method. I replaced Your with Another in the new method.

bash-3.2$ vi src/main/java/com/serverless/Handler.java
package com.serverless;

import java.util.Collections;
import java.util.Map;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class Handler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {

	private static final Logger LOG = Logger.getLogger(Handler.class);

	@Override
	public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
		BasicConfigurator.configure();

		LOG.info("received: " + input);
		Response responseBody = new Response("Go Serverless v1.x! Your function executed successfully!", input);
		return ApiGatewayResponse.builder()
				.setStatusCode(200)
				.setObjectBody(responseBody)
				.setHeaders(Collections.singletonMap("X-Powered-By", "AWS Lambda & serverless"))
				.build();
	}

	public ApiGatewayResponse handleAnotherRequest(Map<String, Object> input, Context context) {
		Response responseBody = new Response("Go Serverless v1.x! Another function executed successfully!", input);
		return ApiGatewayResponse.builder()
				.setStatusCode(200)
				.setObjectBody(responseBody)
				.setHeaders(Collections.singletonMap("X-Powered-By", "AWS Lambda & serverless"))
				.build();
	}
}

  • We then define the individual functions, like so -
    functions:
    hello:
      handler: com.serverless.Handler::handleRequest
    hello2:
      handler: com.serverless.Handler::handleAnotherRequest
    

Notice that we’ve added another function, hello2 which has the same class, but maps to a different handler method.

  • Now we’re ready to deploy and invoke the functions.

This does not work with invoke local as of this writing (1.26.0).

We need to build the project using mvn

bash-3.2$ mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building hello dev
[INFO] ------------------------------------------------------------------------
[INFO]
......
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.304 s
[INFO] Finished at: 2018-01-15T18:12:53-08:00
[INFO] Final Memory: 25M/310M
[INFO] ------------------------------------------------------------------------
bash-3.2$

Now we deploy -

bash-3.2$ serverless deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...
Service Information
service: aws-java-maven
stage: dev
region: us-east-1
stack: aws-java-maven-dev
api keys:
  None
endpoints:
  None
functions:
  hello: aws-java-maven-dev-hello
  hello2: aws-java-maven-dev-hello2

Finally, we can test -

bash-3.2$ serverless invoke -f hello
{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.x! Your function executed successfully!\",\"input\":{}}",
    "headers": {
        "X-Powered-By": "AWS Lambda & serverless"
    },
    "isBase64Encoded": false
}
bash-3.2$ serverless invoke -f hello2
{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.x! Another function executed successfully!\",\"input\":{}}",
    "headers": {
        "X-Powered-By": "AWS Lambda & serverless"
    },
    "isBase64Encoded": false
}
bash-3.2$

As you can notice, the two functions can be invoked using their function names, while residing in the same Handler class. This is pretty useful when creating CRUD operations on resources without having to create a Handler class for each method.

Found this useful? Let me know in the comments below!

Comments