Skip to content
This repository has been archived by the owner on Jun 23, 2021. It is now read-only.

AWS Lambda

James Hood edited this page Oct 14, 2019 · 6 revisions

This application uses AWS Lambda for executing application logic. This page includes patterns and best practices for working with Lambda that are used in this application.

Use AWS::Serverless::Function to define Lambda functions in CloudFormation

AWS::Serverless::Function is a special resource type provided by AWS SAM, which simplifies configuration of Lambda functions and related resources such as IAM roles, permissions, and event source mappings. When using AWS::Serverless::Function, there are additional best practices:

  1. If your organization allows you to create your own IAM roles within your CloudFormation templates, use SAM policy templates to simplify Lambda function permissions while maintaining least privilege best practices.
  2. For Lambda functions that will synchronously process API requests, enable gradual code deployment via AWS CodeDeploy in production with rollback triggers on API CloudWatch alarms. AWS SAM makes this configuration very simple.

Examples in this project:

  1. Using AWS::Serverless::Function to define the API Lambda function.
  2. Simplifying Lambda function permissions using SAM policy templates.
  3. Enabling gradual code deployment. Note, a conditional is used so deployment is gradual in production, but fast in development and test environments. Gradual code deployment is configured to automatically fail the deployment and rollback if configured API alarms go into alarm state during the gradual code deployment.

Configure async Lambda functions with a Dead-letter Queue (DLQ)

Async Lambda functions are frequently used to process events from various sources, e.g., DynamoDB streams and SQS queues. In the event the Lambda function throws an error attempting to process an event, the Lambda service will retry the request a few times. If the failure continues, Lambda's default behavior is to drop the event. However, if you configure a dead-letter queue (DLQ) for the Lambda function, Lambda will write the failed event to the DLQ. In a production system, it's critical to configure all async processing Lambda functions with a DLQ to ensure events are not dropped and can instead be debugged and redriven. Here are some best practices for working with Lambda DLQs.

  1. Prefer Amazon SQS to Amazon SNS for Lambda DLQs. SQS can hold messages for up to 14 days and works well for automating message replay.
  2. Events should be written to the DLQ in the same format that the async processing Lambda function expects. This allows events on the DLQ to be redriven directly through the same async processing Lambda function rather than having to write some custom business logic for processing DLQ events.
  3. If a Lambda function is already being triggered from an SQS queue, configure the SQS DLQ on the SQS queue directly instead of on the Lambda function. Both Lambda and SQS provide DLQ features. We prefer to use the SQS DLQ feature when it's available since it gives more control over the number of retries and ensures the event stored in the DLQ is in the same format as the event stored in the source SQS queue for easier redrive.
  4. For stream-based Lambda functions, unless strict write ordering is critical to your application, e.g., you're replicating a DynamoDB table to another DynamoDB table, use an event fanout solution that supports DLQ. This is because Lambda does not natively support writing failed events to a DLQ for stream-based Lambda functions, which can cause stream processing to get blocked if the Lambda function cannot process an event in the stream. For example, this application uses an app to fanout DynamoDB stream events to Amazon EventBridge.

Examples in this project:

  1. The aws-dynamodb-stream-eventbridge-fanout serverless app is nested in the analytics stack and configured to attach to the Applications table's DynamoDB stream in order to forward the stream events to Amazon EventBridge. This allows multiple Lambda functions to process stream events since there's a limit of how many Lambda functions can be attached directly to a DynamoDB stream without being throttled. The fanout app writes events to an SQS DLQ if they cannot be written successfully to Amazon EventBridge. The DLQ is available as an output of the app for alarming or redriving purposes.

Use a serverless API router library

For Lambda functions that service API requests from Amazon API Gateway, we've found it best to use a library to automatically route API requests to the right handler method within your Lambda function code. Popular examples of serverless API router libraries are aws-serverless-java-container (Java) and aws-serverless-express (Node.js).

Whether all API operations should be handled by a single Lambda function or separate Lambda functions is still a heated topic within the serverless community. However, we've found using an API router library gives you the flexibility to choose either option or something in between, without having to change your Lambda function code.

Examples in this project:

  1. This project uses aws-serverless-java-container for Jersey. Jersey is an implementation of JAX-RS, the Java API for RESTful Web Services.
  2. The swagger-codegen-maven-plugin is configured to generate API model classes based on the OpenAPI API definition. At build time, this generates Java classes for all API request and response objects as well as a JAX-RS annotated Java interface for the API itself.
  3. The ApplicationService class implements the generated interface and supplies the business logic of each API operation. For example, it provides the business logic of the CreateApplication operation.
  4. Within the Lambda handler class for the API Lambda function, the Jersey application is configured and passed to a JerseyLambdaContainerHandler handler instance provided by the aws-serverless-java-container library. All Lambda requests are passed to the handler, which routes the request to the right method in the ApplicationsService class.

Optimize the AWS Java SDK for better coldstart performance

Use best practices around configuring the AWS Java SDK for use in Lambda environments:

  1. Use the AWS Java SDKv2 with suggested optimizations for Lambda.
  2. Add dependencies on only the specific service clients needed for the project to minimize overall size of the Lambda binary.
  3. Customize SDK timeout and retry configuration for your application. The default SDK timeout and retry values may not make sense for the AWS service being called or the constraints of the backend service environment. In the worst case, the default settings can result in high latencies and/or availability risks to the backend service.

Examples in this project:

  1. Adding dependencies on the minimum AWS Java SDKv2 components needed by the backend: url-connection-client, DynamoDB, and KMS.
  2. Following suggested SDK configuration for Lambda environments.
  3. Customizing timeout and retry policy for DynamoDB client. Rationale: DynamoDB requests should normally have single digit millisecond response times so allow many retries, but cap the total request duration at 1 second to ensure API latency doesn't get out of control if the service is taking longer than usual to respond.