Serverless computing with AWS Lambda, Part 2: AWS Lambda with DynamoDB

Integrate AWS Lambda with DynamoDB, then call Lambda functions from a Java client

1 2 Page 2
Page 2 of 2

Calling AWS Lambda functions from a Java application

Now that we have a function running in AWS Lambda, we'll write a client application in Java that can call it. In order to execute a Lambda function from a Java application that isn't running in AWS, we first need to create an IAM user with permissions to invoke the Lambda function. We can then use those credentials from our client application.

All of this is easy conceptually, but there are a lot of steps in the AWS console. I'll walk you through them with screenshots.

Step 1. Create the AWS user

We don't want to embed our primary user credentials into a Java application, so we're going to create a new user with fewer privileges. Navigate to the AWS console, choose Services, and find IAM under Security, Identity & Compliance. Click on Users and then the Add user button, as shown below.

jw osjp awslambda fig7 Steven Haines

Figure 7. Add user

Give your user a name, such as get-widget-lambda-user and check the Programmatic access checkbox and press Next: Permissions, as shown below.

jw osjp awslambda fig8 Steven Haines

Figure 8. Set user access

Next, click on the Create Group button.

jw osjp awslambda fig9 Steven Haines

Figure 9. Create a group for your user

While we can add inline policies to users, it is a better practice to create a group that manages policies for you, and then add the user to that group. Enter a name for your group, then click Create policy.

jw osjp awslambda fig10 Steven Haines

Figure 10. Create a group for your user

Step 2. Create a custom access policy

We could create a manual policy but to try to make things easier I searched the existing policies first. Unfortunately, the only existing policy for granting lambda:InvokeFunction permissions is the Lambda All Access policy. This basically gives the user root access to all Lambda functionality, which is not a good idea.

We'll need to create a custom policy to restrict access for a programatic user. First, we'll build a policy using the policy generator. To start, click on Create policy. You should see a screen like this one:

jw osjp awslambda fig11 Steven Haines

Figure 11. Create policy

Click Select at the end of the Policy Generator line. This will bring up the Edit Permissions page shown here:

jw osjp awslambda fig12 Steven Haines

Figure 12. Edit Permissions

Choose AWS Lambda from the AWS Service dropdown, then click on Actions and choose InvokeFunction. Add the following for the ARN:

arn:aws:lambda:*

This policy allows the InvokeFunction action to be performed on any AWS Lambda function. You could further restrict this policy to limit invocation to a single AWS Lambda by copying the ARN for the AWS Lambda itself, which is on the top of the Lambda's dashboard page. Mine is:

arn:aws:lambda:us-east-1:YOUR_ACCOUNT_NUMBER:function:get-widget

When you are ready, press Add Statement to add this permission to your policy. Press Next Step to review the policy, as shown here.

jw osjp awslambda fig13 Steven Haines

Figure 13. Review policy

The important thing on this page is to set a policy name, otherwise your policy will be named "policygen" followed by some random numbers. Press the Create Policy button. Because you had to create the policy in a new window, go back to the user-creation screen, press the Refresh button, and find and select your policy, as shown:

jw osjp awslambda fig14 Steven Haines

Figure 14. Refresh the Create Group page

If it's difficult to find your newly created policy, you can use filters to help. As an example, try selecting customer managed policies and enter the name "lambda-invoke". Press Create Group and it will return you to your user-creation workflow with your new group:

jw osjp awslambda fig15 Steven Haines

Figure 15. Select group

Finally, review your user and press Create User:

jw osjp awslambda fig16 Steven Haines

Figure 16. Review user

The last page is important because this is where you can see your access key ID and secret access key. If you press the Show link, it will reveal your secret access key. You'll need to add this information to your Java code in the next section, so be sure to preserve it.

jw osjp awslambda fig17 Steven Haines

Figure 17. New user

That completes the policy setup. So far you have

  1. Created a new policy allowing the invocation of any Lambda function in your account.
  2. Created a group that has this single policy.
  3. Created a user and added it to this group.
  4. Saved your access key ID and secret access key, which you'll add to your Java code in the next section.

Step 3. Create a Lambda client class

Next we'll write the code to connect to AWS and invoke our function. Listing 3 shows the source code for the WidgetLambdaClient class.

Listing 3. WidgetLambdaClient.java


package com.javaworld.awslambda.widget.client;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.AWSLambda;
import com.amazonaws.services.lambda.AWSLambdaClientBuilder;
import com.amazonaws.services.lambda.model.InvokeRequest;
import com.amazonaws.services.lambda.model.InvokeResult;

public class WidgetLambdaClient {

    public static void main(String[] args) {
        // Setup credentials
        BasicAWSCredentials awsCreds = new BasicAWSCredentials("YOUR_ACCESS_KEY_ID", "YOUR_SECRET_ACCESS_KEY");

        // Create an AWSLambda client
        AWSLambda lambda = AWSLambdaClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCreds)).withRegion(Regions.US_EAST_1).build();

        // Create an InvokeRequest
        InvokeRequest request = new InvokeRequest()
                .withFunctionName("get-widget")
                .withPayload("{ \"id\": \"1\"}");

        try {
            // Execute the InvokeRequest
            InvokeResult result = lambda.invoke(request);

            // We should validate the response
            System.out.println("Status Code: " + result.getStatusCode());

            // Get the response as JSON
            String json = new String(result.getPayload().array(), "UTF-8");

            // Show the response; we could use a library like Jackson to convert this to an object
            System.out.println(json);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

We begin by creating a BasicAWSCredentials instance with our access key and secret key, which you preserved in Step 2. Our main interface into AWS Lambda is the AWSLambda class, which can be created using the AWSLambdaClientBuilder. We invoke standard() to create it and then we add our user credentials, which we've wrapped in an AWSStaticCredentialsProvider instance.

Lambda functions are created in specific regions, so you'll need to set the region. If you aren't sure where you created your Lambda function, navigate back to your Lambda page and look at the ARN in the upper-right side of the page. Or you can look at the upper-right side of the top toolbar next to your name on the Lambda page in the AWS console. As an example, I am running in "N. Virginia", which is "us-east-1". Use this website to translate the physical location to the region. Once you've set the region, call build() to create the AWSLambda instance.

Step 4. Invoke the Lambda function

There are different ways of invoking Lambdas. In this case we'll opt for the most straightforward (and manual) way, of sending an InvokeRequest to the function and receiving an InvokeResult back. To start, create a new InvokeRequest instance, call withFunction() to tell it the name of the function you want to invoke, and then pass the payload you want to send via the withPayload() method. This payload should look just like the one we used for testing in the AWS Lambda dashboard.

We pass the InvokeRequest to AWSLambda's invoke() method and it returns an InvokeResult instance. The getStatusCode() method will tell us if it succeeded (returning a 200 response) or failed (returning a non-2xx response.) In a production application you should examine the status code after every Lambda invocation and respond accordingly.

We can retrieve the body of the response by calling the InvokeResult's getPayload() method, which returns a ByteBuffer. We can convert this to a raw JSON string by converting the ByteBuffer to an array, by calling its array() method, and then passing that array to the String's constructor. If we wanted to convert this into an object, we could use a tool like Jackson or Gson to deserialize the JSON into an object.

Make an executable JAR file

Finally, to make running this code easier, we'll add the maven-jar-plugin to the POM file, referencing our WidgetLambdaClient in the mainClass:

Listing 8. Add the maven-jar plugin to the Maven POM file


            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.javaworld.awslambda.widget.client.WidgetLambdaClient</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

Step 5. Build and monitor the Lambda client class

Build with mvn clean install and then you can execute from the target directory with the following command:

java -jar aws-lambda-java-1.0-SNAPSHOT.jar

When I run this code, I see the following output:

Status Code: 200
{"id":"1","name":"Widget 1"}

If you run into problems, check that you have the proper credentials configured and that your IAM user has the proper permissions to execute your function. You could also check the AWS Lambda logs, which you may access either through the Lambda dashboard or directly from CloudWatch:

  1. From your Lambda page, click on the Monitoring tab and then choose View Logs in CloudWatch in the upper-right corner
  2. From the Services menu, choose CloudWatch, under Management Tools, as shown in Figure 18. Select Logs from the left panel and then choose the aws/lambda/get-widget log group.
jw osjp awslambda fig18 Steven Haines

Figure 18. Opening CloudWatch

Accessing logs through your Lambda page will automatically filter to your Lambda's filter group, but regardless of how you got there, you should see something similar to Figure 19.

jw osjp awslambda fig19 Steven Haines

Figure 19. CloudWatch logs for your Lambda's log group

Each Log Stream represents the periodic rollup of logs for a specific Lamdba. You can click on one of the entries to see the contents of the log; for example, I updated the client to request the Widget with ID "2", which does not exist. Figure 20 shows the contents of the logs for that execution.

jw osjp awslambda fig20 Steven Haines

Figure 20. CloudWatch logs showing an error

In this example you can see a successful execution (start and end without any custom logging) followed by an error ("No widget found with ID: 2"). Logs are a good tool to help you troubleshoot Lambdas, especially ones executed asynchronously.

Conclusion

This two-part tutorial has introduced you to serverless computing with AWS Lambda. In Part 1 we answered the question, "What is serverless computing, anyway?" and I explained the relationship between serverless computing, microservices, and nanoservices architectures. You got your first look at AWS Lambda and we built, deployed, and tested our first Lambda function in Java.

In Part 2, we've added support for Amazon's DynamoDB, enabling our Lambda function to retrieve a live Widget from DynamoDB instead of creating one on-the-fly. You learned how to interact with DynamoDB using the DynamoDBMapper API. We created a Java client application that could invoke our Lambda function, and you learned how to create a new IAM user, group, and custom policy. Finally, we built the updated Lambda function and reviewed logs captured and sent to CloudWatch for troubleshooting.

1 2 Page 2
Page 2 of 2