SAM DynamoDB Application for Managing Orders
This is a sample application to demonstrate how to build an application on DynamoDB using the DynamoDBMapper ORM framework to map Order items in a DynamoDB table to a RESTful API for order management.
.
├── README.md <-- This instructions file
├── LICENSE.txt <-- Apache Software License 2.0
├── NOTICE.txt <-- Copyright notices
├── pom.xml <-- Java dependencies, Docker integration test orchestration
├── src
│ ├── main
│ │ └── java
│ │ ├── com.amazonaws.config <-- Classes to manage Dagger 2 dependency injection
│ │ │ ├── OrderComponent.java <-- Contains inject methods for handler entrypoints
│ │ │ └── OrderModule.java <-- Provides dependencies like the DynamoDB client for injection
│ │ ├── com.amazonaws.dao <-- Package for DAO objects
│ │ │ └── OrderDao.java <-- DAO Wrapper around the DynamoDBTableMapper for Orders
│ │ ├── com.amazonaws.exception <-- Source code for custom exceptions
│ │ ├── com.amazonaws.handler <-- Source code for lambda functions
│ │ │ ├── CreateOrderHandler.java <-- Lambda function code for creating orders
│ │ │ ├── CreateOrdersTableHandler.java <-- Lambda function code for creating the orders table
│ │ │ ├── DeleteOrderHandler.java <-- Lambda function code for deleting orders
│ │ │ ├── GetOrderHandler.java <-- Lambda function code for getting one order
│ │ │ ├── GetOrdersHandler.java <-- Lambda function code for getting a page of orders
│ │ │ └── UpdateOrderHandler.java <-- Lambda function code for updating an order
│ │ └── com.amazonaws.model <-- Source code for model classes
│ │ ├── request <-- Source code for request model classes
│ │ │ ├── CreateOrderRequest.java <-- POJO shape for creating an order
│ │ │ ├── GetOrDeleteOrderRequest.java <-- POJO shape for getting or deleting an order
│ │ │ ├── GetOrdersRequest.java <-- POJO shape for getting a page of orders
│ │ │ └── UpdateOrderRequest.java <-- POJO shape for updating an order
│ │ ├── response <-- Source code for response model classes
│ │ │ ├── GatewayResponse.java <-- Generic POJO shape for the APIGateway integration
│ │ │ └── GetOrdersResponse.java <-- POJO shape for a page of orders
│ │ └── Order.java <-- POJO for Order resources
│ └── test <-- Unit and integration tests
│ └── java
│ ├── com.amazonaws.config <-- Classes to manage Dagger 2 dependency injection
│ ├── com.amazonaws.dao <-- Tests for OrderDao
│ ├── com.amazonaws.handler <-- Unit and integration tests for handlers
│ │ ├── CreateOrderHandlerIT.java <-- Integration tests for creating orders
│ │ ├── CreateOrderHandlerTest.java <-- Unit tests for creating orders
│ │ ├── DeleteOrderHandlerTest.java <-- Unit tests for deleting orders
│ │ ├── GetOrderHandlerTest.java <-- Unit tests for getting one order
│ │ ├── GetOrdersHandlerTest.java <-- Unit tests for getting a page of orders
│ │ └── UpdateOrderHandlerTest.java <-- Unit tests for updating an order
│ └── com.amazonaws.services.lambda.runtime <-- Unit and integration tests for handlers
│ └── TestContext.java <-- Context implementation for use in tests
└── template.yaml <-- Contains SAM API Gateway + Lambda definitions
Requirements
- AWS CLI already configured with at least PowerUser permission
- Java SE Development Kit 8 installed
- Docker installed
- Maven
- SAM CLI
- Python 3
Setup process
Installing dependencies
We use maven
to install our dependencies and package our application into a JAR file:
mvn package
Local development
Invoking function locally through local API Gateway
- Start DynamoDB Local in a Docker container.
docker run -p 8000:8000 amazon/dynamodb-local
- Create the DynamoDB table.
aws dynamodb create-table --table-name orders_table --attribute-definitions AttributeName=orderId,AttributeType=S --key-schema AttributeName=orderId,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://localhost:8000
- Start the SAM local API.
- On a Mac:
sam local start-api --env-vars src/test/resources/test_environment_mac.json
- On Windows:
sam local start-api --env-vars src/test/resources/test_environment_windows.json
- On Linux:
sam local start-api --env-vars src/test/resources/test_environment_linux.json
If the previous command ran successfully you should now be able to hit the following local endpoint to
invoke the functions rooted at http://localhost:3000/orders
SAM CLI is used to emulate both Lambda and API Gateway locally and uses our template.yaml
to
understand how to bootstrap this environment (runtime, where the source code is, etc.) - The
following excerpt is what the CLI will read in order to initialize an API and its routes:
...
Events:
GetOrders:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /orders
Method: get
Packaging and deployment
AWS Lambda Java runtime accepts either a zip file or a standalone JAR file - We use the latter in
this example. SAM will use CodeUri
property to know where to look up for both application and
dependencies:
...
GetOrdersFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: target/aws-sam-java-rest-1.0.0.jar
Handler: com.amazonaws.handler.GetOrdersHandler::handleRequest
Firstly, we need a S3 bucket
where we can upload our Lambda functions packaged as ZIP before we
deploy anything - If you don't have a S3 bucket to store code artifacts then this is a good time to
create one:
export BUCKET_NAME=my_cool_new_bucket
aws s3 mb s3://$BUCKET_NAME
Next, run the following command to package our Lambda function to S3:
sam package \
--template-file template.yaml \
--output-template-file packaged.yaml \
--s3-bucket $BUCKET_NAME
Next, the following command will create a Cloudformation Stack and deploy your SAM resources.
sam deploy \
--template-file packaged.yaml \
--stack-name sam-orderHandler \
--capabilities CAPABILITY_IAM
See Serverless Application Model (SAM) HOWTO Guide for more details in how to get started.
After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL:
aws cloudformation describe-stacks \
--stack-name sam-orderHandler \
--query 'Stacks[].Outputs'
Testing
Running unit tests
We use JUnit
for testing our code.
Unit tests in this sample package mock out the DynamoDBTableMapper class for Order objects.
Unit tests do not require connectivity to a DynamoDB endpoint. You can run unit tests with the
following command:
mvn test
Running integration tests
Integration tests in this sample package do not mock out the DynamoDBTableMapper and use a real AmazonDynamoDB client instance. Integration tests require connectivity to a DynamoDB endpoint, and as such the POM starts DynamoDB Local from the Dockerhub repository for integration tests.
mvn verify
Running end to end tests through the SAM CLI Local endpoint
Running the following end-to-end tests requires Python 3 and the requests
pip
package to be installed. For these tests to succeed,
pip3 install requests
python3 src/test/resources/api_tests.py 3
The number that follows the test script name is the number of orders to create in the test. For these tests to work, you must follow the steps for local development.
Appendix
AWS CLI commands
AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack:
sam package \
--template-file template.yaml \
--output-template-file packaged.yaml \
--s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME
sam deploy \
--template-file packaged.yaml \
--stack-name sam-orderHandler \
--capabilities CAPABILITY_IAM \
--parameter-overrides MyParameterSample=MySampleValue
aws cloudformation describe-stacks \
--stack-name sam-orderHandler --query 'Stacks[].Outputs'
Bringing to the next level
Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: