We are now ready to implement our first Lambda function, which will just upload to the cloud via AWS CLI and invoke manually.
First, we have to create a new subproject, like we did earlier. This time, the subproject will be called lambda-test. We can easily do that with these two commands:
We can create a blank class in Handler.java like this:
package com.serverlessbook.lambda.test;
public class Handler {}
Note that we've already chosen a naming convention for package naming: while our base Lambda package sits in the com.serverlessbook.lambda package, individual Lambda functions are in packages named with the com.serverlessbook.lambda.{function-name} format. We will also call handler classes Handler because it sounds perfect in English: Handler implements LambdaHandler. This naming convention is, of course, up to you and your team, but it is convenient to keep things organized.
If you are already familiar with the Gradle build mechanism, you might have realized that before we proceed to implement Lambda's handler function, we have to add the lambda subproject to lambda-test as a dependency, and that is a very valid point. The easiest way to do that would be by creating a build.gradle file for the lambda-test subproject, add the dependency in the dependencies {} block, and move on. On the other hand, we know that our project will include more than one Lambda function, and all of them will share the same build configuration. Putting this configuration in a central location is a very good idea for clear organization and maintainability. Fortunately, Gradle is a very powerful tool that allows such scenarios. We can create a build configuration block in our root project and apply this configuration only to subprojects whose name starts with lambda-, in accordance with our subproject naming convention. Then, we can edit our root build.gradle and add this block to the end of the file:
configure(subprojects.findAll()) { if (it.name.startsWith("lambda-")) { } }
It tells Gradle to apply this configuration only to Lambda projects. Inside this block, we will have an important configuration, but for now, we can start with the most important dependency and edit the block to appear like this:
In this step, we have to add another important build configuration, which is the Shadow plugin. The Shadow plugin creates an uber-JAR (also known as a fat JAR or JAR with dependencies) that is required by AWS Lambda. After each build phase, this plugin will compile all the dependencies along with that project's source into a second-and bigger-JAR file, which will be our deployment package for AWS Lambda. To install this plugin, first, we have to edit the buildscript configuration of the root build.gradle file. After editing, the buildscript section should look like this:
We have to apply the plugin to all lambda functions. We have to add two lines to the lambda subproject's configuration, and the final version should look like this:
The first line applies the Shadow plugin, which adds shadowJar task to every lambda subproject. The second directive ensures that after every build task, the shadowJar is automatically executed, thus an uber-JAR is placed into the build directory.
You can try our basic build configuration by running this command in the root directory:
$ ./gradlew build
You can see the uber-JAR file lambda-test-1.0-all.jar in the lambada-test/build/libs directory.
Now we are going to implement the handler function with very basic functionality, like what we did previously to test the base handler. For the sake of simplicity, we will define input and output classes as inner static classes, although this is not the recommended way of creating classes in Java. Now open the Handler class and edit it like this:
package com.serverlessbook.lambda.test;
import com.amazonaws.services.lambda.runtime.Context;
import com.serverlessbook.lambda.LambdaHandler;
public class Handler extends LambdaHandler<Handler.TestInput, Handler.TestOutput> {
static class TestInput {
public String value;
}
static class TestOutput {
public String value;
}
@Override
public TestOutput handleRequest(TestInput input, Context context) {
TestOutput testOutput = new TestOutput();
testOutput.value = input.value;
return testOutput;
}
}
That's it; we have now a very basic Lambda function, which is ready to deploy to the cloud. In the next section, we will deploy and run it on AWS Lambda runtime.