Multiple Functions in Serverless Framework with Java
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 calledhandleRequest
which maps to the lambda functionhello
, as noticed in theserverless.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!