Updated

Serverless computing with AWS Lambda, Part 1

Get an overview of AWS Lambda's nanoservices architecture and execution model, then build your first Lambda function in Java

1 2 3 Page 2
Page 2 of 3

The Context object provides information about your function and the environment in which it is running, such as the function name, its memory limit, its logger, and the amount of time remaining, in milliseconds, that the function has to complete before AWS Lambda kills it.

With that overview out of the way, we'll spend the remainder of Part 1 building a simple Lambda function that returns a widget. For the purposes of our example application, the user requests a widget by ID (a String), which we'll wrap into a WidgetRequest object. The function will then return a Widget object as its response.

Building a Lambda function

Listing 2 shows the source code for Widget, which is a POJO with an id and name:

Listing 2. Widget.java


package com.javaworld.awslambda.widget.model;
public class Widget {
    private String id;
    private String name;
    public Widget() {
    }
    public Widget(String id, String name) {
        this.id = id;
        this.name = name;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Listing 3 shows the source code for a WidgetRequest, which is a POJO that contains an id:

Listing 3. WidgetRequest.java

package com.javaworld.awslambda.widget.model;
public class WidgetRequest {
    private String id;
    public WidgetRequest() {
    }
    public WidgetRequest(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}

Next, we'll build our lambda request handler. Listing 4 shows the source code for the GetWidgetHandler class:

Listing 4. GetWidgetHandler.java

package com.javaworld.awslambda.widget.handlers;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.javaworld.geekcap.awslambda.widget.model.Widget;
import com.javaworld.geekcap.awslambda.widget.model.WidgetRequest;
public class GetWidgetHandler implements RequestHandler<WidgetRequest, Widget> {
    @Override
    public Widget handleRequest(WidgetRequest widgetRequest, Context context) {
        return new Widget(widgetRequest.getId(), "My Widget " + widgetRequest.getId());
    }
}

The GetWidgetHandler class implements the RequestHandler interface, accepting a WidgetRequest and returning a Widget. The WidgetRequest includes an id parameter to indicate which Widget is being requested. For the purposes of this example, however, we won't load the Widget from a database. Instead, we'll build and return a new Widget instance with the specified ID and the name "My Widget ID." We'll do this on the fly.

Listing 5 shows the contents of the Maven POM file that can build a JAR file containing our Lambda function.

Listing 5. pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.javaworld.geekcap</groupId>
    <artifactId>aws-lambda-java</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>aws-lambda-java</name>
    <url>http://maven.apache.org</url>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The POM file includes the aws-lambda-java-core dependency and builds against Java 8. The maven-shade-plugin packages all of our dependent JAR files inside of our JAR file so that it can run standalone. (See the AWS Lambda documentation to learn more about creating a standalone JAR file with maven-shade-plugin and using it with AWS Lambda.)

To run this build, execute the following command:

mvn clean install

The command creates a file named aws-lambda-java-1.0-SNAPSHOT.jar in your target directory. We'll upload this file to AWS Lambda in the next section.

Creating a Lambda function in the AWS console

Now that we have a Lambda function written and packaged into a standalone JAR file, let's set it up in AWS Lambda. Before proceeding with this step, you'll need to setup a free AWS account. Go ahead and do that now.

Once you have your account, login and open the AWS console. Click on Services, then choose Lambda in the Compute section, as shown in Figure 3.

awslambdap1 fig03 Steven Haines

Figure 3. Accessing the Lambda page in AWS Lambda

Click the Create a Lambda function button and select Blank Function, as shown in Figure 4.

awslambdap1 fig04 Steven Haines

Figure 4. Creating a blank function

Configuring triggers

At the time of this writing, there are no blueprints for creating Java Lambda functions, so you'll need to start the function from scratch. The first screen that you will see after selecting a blank function is the Configure Triggers page, shown in Figure 5.

awslambdap1 fig05 Steven Haines

Figure 5. Configuring triggers

You will setup triggers to instruct AWS to call your Lambda function when something occurs. If you click on the rounded rectangle on the left side of this page, you'll see the types of things that can trigger Lambda functions, including:

  • API Gateway: Allows a call to a RESTful resource to be forwarded to your function.
  • AWS IoT: Calls your function in response to an IoT (Internet of Things) events.
  • Alexa Skills Kit: Allows you to create Alexa voice-activate skills.
  • Alexa Smart Home: Handles Alexa Smart Home device events.
  • CloudFront: Calls your function in response to CloudFront events, allowing you to customize content delivered by CloudFront (AWS's CDN solution).
  • CloudWatch Events: Calls your function when a CloudWatch event or alert occurs.
  • CloudWatch Logs: Calls your function when specific messages are logged to CloudWatch.
  • CodeCommit: Calls your function when code is committed to CodeCommit, which is AWS's CI solution.
  • DynamoDB: Calls your function when data is inserted into a DynamoDB table.
  • Kinesis: Calls your function in response to analytics events.
  • S3: Calls your function when a file is uploaded to S3.
  • SNS: Calls your function when a notification is published to the Simple Notification Service.

We'll leave this blank for now, because we're going to call our Lambda function directly.

Configuring your function

Click Next and you'll see the Configure Function screen shown in Figure 6.

awslambdap1 fig06 Steven Haines

Figure 6. Configuring a function

Give your function a name (in this case it's "get-widget") and a description. Choose Java 8 as your runtime, which will add a dropdown that allows you to choose a JAR file to upload. Click the Upload button and find the JAR file that you built earlier in this tutorial. Below this, you'll see the page to configure your Lambda function handler and role, as shown in Figure 7.

awslambdap1 fig07 Steven Haines

Figure 7. Configuring the Lambda function handler and role

Fist, you'll configure the handler. The handler's format is as follows:

package.Class::handlerMethod

For our example, the handler is:

com.javaworld.awslambda.widget.handlers.GetWidgetHandler::handleRequest

Next, you'll specify a role for the function. Before doing that, let's make sure you understand roles in AWS Lambda.

Configuring roles in AWS Lambda

Roles define policies that grant the executor (in this case a Lambda function) permission to interact with AWS Lambda and other AWS resources. For example, if your Lambda function was going to query a DynamoDB instance, then it would need access permissions for dynamodb:Scan and probably dynamodb:GetItem. If your Lambda was going to add an object to S3, it would need access permission to s3:PutObject on your S3 resource.

The important thing about roles is that they grant or deny permissions for your Lambda function to interact with other AWS resources. Roles have nothing to do with application users or your application's internal configuration. Rather, they define how your application can or cannot interact with AWS resources.

For this example, we don't need any special permissions because our Lambda function doesn't actually do anything. We do need to be able to access DynamoDB for the examples in Part 2, however. To setup that permission, we'll start by creating a new role from a template. We'll name it get-widget-role and add the policy template "Simple Microservice Permissions." The Simple Microservice Permissions role is part of an existing Lambda blueprint, and provides full access to DynamoDB. Its contents are shown in Listing 6.

Listing 6. Simple Microservices Permissions

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:YOUR_ACCOUNT_NUMBER:table/*"
        }
    ]
}

This policy reads as follows:

Allow the specified actions (DeleteItem, GetItem, etc) on resource arn:aws:dynamodb:us-east-1:YOUR_ACCOUNT_NUMBER:table/*, where YOUR_ACCOUNT_NUMBER will be your account number and table/* means all of your tables in DynomDB.

If you wanted to refine the policy further, you could specify a single table name instead of all tables; or you could change the list of allowable actions. For example, a function that simply retrieves an item from DynamoDB does not need permissions to delete, put, or update items, so you might remove those.

Configuring Lambda's advanced settings

Finally, expand the Advanced Settings section, which is shown in Figure 8.

awslambdap1 fig08 Steven Haines

Figure 8. Advanced Settings for Lambda function

Once again, we can leave most of this alone. Because we pay for Lambda executions both by execution time and by the amount of RAM that we use, we can reduce the memory requirement from 512MB to 128MB. You may be thinking that 128MB is not a lot of memory for a Java application, but recall the Lambda functions are small and simple. A function is not like a servlet container that starts a web application and leaves it running for hours or days on end, so for most purposes 128MB is just fine.

Another important thing to notice on this screen is the timeout. Because Lambda functions are meant to be short-lived and you pay for execution time, AWS Lambda allows you to specify a timeout. If your function does not complete within the allotted time, it will be killed. This protects your cloud bill in case things go wrong.

When you're ready, press Next. Review the summary of your function, then press Create Function.

Testing your AWS Lambda function

Now that you have your function deployed in AWS Lambda, let's use the AWS console to test it out. After creating your function, you should have been dropped off on your function's page (Lambda > Functions > get-widget). If you need to, you can always navigate through Services to Lambda, then click on Functions and choose your function name (in this case, get-widget). Either way, you'll land on the page shown in Figure 9.

awslambdap1 fig09 Steven Haines

Figure 9. Lambda function page

To test your function, click the blue Test button and you will be prompted to define a new test event. Recall that the Lambda function accepts a WidgetRequest, which has a single id parameter. Because you implemented the RequestHandler interface, AWS Lambda will deserialize the JSON that you pass in your test event into a WidgetRequest object. All of this is to say that to test the function you only need to specify an id field, as shown in Figure 10.

awslambdap1 fig10 Steven Haines

Figure 10. Lambda test event

1 2 3 Page 2
Page 2 of 3