How to create a REST API in Java using DynamoDB and Serverless

This page summarizes the projects mentioned and recommended in the original post on dev.to

Our great sponsors
  • Scout APM - Less time debugging, more time building
  • JetBrains - Developer Ecosystem Survey 2022
  • SonarQube - Static code analysis for 29 languages.
  • aws-java-products-api

    A serverless REST API service for products deployed to AWS. The data is stored in a DynamoDB table.

    Originally posted at Serverless In this walkthrough, we will build a products-api serverless service that will implement a REST API for products. We will be using Java as our language of choice. The data will be stored in a DynamoDB table, and the service will be deployed to AWS. What we will cover: Pre-requisites Creating the REST API service Deep dive into the Java code Deploying the service Calling the API Install Pre-requisites Before we begin, you’ll need the following: Install node and npm Install the Serverless Framework installed with an AWS account set up. Install Oracle JDK and NOT Java JRE. Set the following: export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-10.jdk/Contents/Home Install Apache Maven. After downloading and installing Apache Maven, add the apache-maven-x.x.x folder to the PATH environment variable. Testing Pre-requisites Test Java installation: Test Maven installation: Create the Serverless project Let’s create a project named products-api, using the aws-java-maven boilerplate template provided by the Serverless Framework, as shown below: Updating the project The serverless boilerplate template aws-java-maven gives us a nice starting point and builds a fully functional service using Java and Maven. However, we'll need to adapt it to our Products API service that we are building. Let’s update the project hello to products-api in the POM i.e. pom.xml: The serverless.yml Let’s add the relevant Lambda handlers under functions and update the deployment artifact under package, as per our project requirements. Update the following sections: Managing the DynamoDB table Since we will be using a DynamoDB table to store our products data, we will let the Serverless Framework manage the DynamoDB resource and the relevant IAM Role permissions for the lambda functions to access the DynamoDB table. Add the following section for iamRoleStatements under the provider section in the serverless.yml file: Now, to create and manage the DynamoDB table from within our serverless project, we can add a resources section to our serverless.yml file. This section describes the DynamoDB resource via a CloudFormation syntax: The DynamoDB Adapter We will create an adapter whose responsibility will be to manage the connection to the specifed DynamoDB table using configuration, like the AWS region where the table will be deployed. The DynamoDB adapter class is a singleton that instantiates a AmazonDynamoDB client and a AWS DBMapper class. Here’s an excerpt from the DynamoDBAdapter class: The Product POJO We have the Product POJO that represents the Product entity and encapsulates all its functionality in a class. The Product POJO class defines a data structure that matches the DynamoDB table schema and provides helper methods for easy management of product data. The AWS SDK provides an easy way to annotate a POJO with DynamoDB specific attributes as defined by Java Annotations for DynamoDB We annotate the Product POJO class with: a DynamoDBTable attribute to specify the DynamoDB table name a DynamoDBHashKey attribute to map a property to a DynamoDB Haskey a DynamoDBRangeKey attribute to map a property to a DynamoDB RangeKey a DynamoDBAutoGeneratedKey attribute to map a property that needs to get a auto-generated id Also, note that we get the product table name from a environment variable PRODUCTS_TABLE_NAME defined in our serverless.yml file: Next, let’s look at the methods that the Product POJO utilizes for data management. The constructor method The public constructor does a couple of things: Overrides the PLACEHOLDER_PRODUCTS_TABLE_NAME table name annotation value with the actual value from the environment variable Gets an instance of DynamoDBAdapter Gets an instance of AmazonDynamoDB client Gets an instance of DynamoDBMapper The list() method The list() method uses a DynamoDBScanExpression construct to retrieve all the products from the products table. It returns a list of products via the List data structure. It also logs the list of products retrieved. The get() method The get() method takes a product id and uses a DynamoDBQueryExpression to set up a query expression to match the passed in product id. The mapper object has a query method that is passed the queryExp, to retrieve the matching product: The save() method The save() method takes a product instance populated with values, and passes it to the mapper object's save method, to save the product to the underlying table: The delete() method The delete() method takes a product id and then calls the get() method to first validate if a product with a matching id exists. If it exists, it calls the mapper object's delete method, to delete the product from the underlying table: Note: The Product POJO class can be independently tested to make sure the data management functionality works against a DynamoDB table. This seperation allows us to write unit tests to test the core DAL functionality. Implementing the API Now, that our Product DAL is written up and works as expected, we can start looking at the API function handlers that will be calling the Product DAL to provide the expected functionality. We will define the API endpoints, then map the events to the handlers and finally write the handler code. The API Endpoints Before we look at the API handlers, let’s take a look at the API endpoints that we will map to the handlers. The API endpoints will look like: POST /products: Create a product and save it to the DynamoDB table. GET /products/: Retrieves all existing products. GET /products/{id}: Retrieves an existing product by id. DELETE /products/{id}: Deletes an existing product by id. Mapping Events to Handlers To implement the API endpoints we described above, we need to add events that map our API endpoints to the corresponsing Lambda function handlers. Update the following sections in the serverless.yml: Writing the Handlers Let’s write the code for the four handlers that will provide us the needed functionality to implement the Products REST API. Let’s copy the Handler.java file that was generated by the boilerplate and create four new files under the src/main/java/com/serverless folder. We can now delete the Handler.java file. CreateProductHandler.java ListProductHandler.java GetProductHandler.java DeleteProductHandler.java The basic code for all the handlers are the same. Each handler is defined as a class that implements RequestHandler from the AWS Lambda runtime. Then the handleRequest method is overriden in the class to provide the custom logic for the handler. The handleRequest method receives a Map object with the inputs from the caller and a Context object with information about the caller's environment. Create Product Handler If the call is successful, a 200 OK response is returned back. In case of an error or exception, the exception is caught and a 500 Internal Server Error response is returned back: List Product Handler The ListProductHandler calls the list() method on the product instance to get back a list of products. If the call is successful, a 200 OK response is returned back. In case of an error or exception, the exception is caught and a 500 Internal Server Error response is returned back: Get Product Handler The GetProductHandler receives the id via the path parameters attribute of the input. Then it calls the get() method on the product instance passes it the id to get back a matching product. If the call is successful, a 200 OK response is returned back. If no products with the matching id are found, a 404 Not Found response is returned back. In case of an error or exception, the exception is caught and a 500 Internal Server Error response is returned back: Delete Product Handler The DeleteProductHandler receives the id via the path parameters attribute of the input. Then it calls the delete() method on the product instance passing it the id to delete the product. If the call is successful, a 204 No Content response is returned back. If no products with the matching id are found, a 404 Not Found response is returned back. In case of an error or exception, the exception is caught and a 500 Internal Server Error response is returned back: Note: The full source code for the project is available on Github. Deploying the service Now that we’ve looked at the code and understand the overall workings of the service, let’s build the Java code, and deploy the service to the cloud. To build the Java code: After a successful build, we should have an artifact at aws-java-products-api/target/products-api-dev.jar that we will use in our deployment step. Let’s deploy the service to the cloud: On a successful deployment, we will have our four API endpoints listed as shown above. Calling the API Now that we have a fully functional REST API deployed to the cloud, let’s call the API endpoints. Create Product Now, we’ll make a few calls to add some products. List Products Here’s the java-products-dev DynamoDB table listing our products: No Product(s) Found: Get Product Product Not Found: Delete Product Product Not Found: View the CloudWatch Logs We have used the log4j.Logger in our Java code to log relevant info and errors to the logs. In case of AWS, the logs can be retrieved from CloudWatch. Let’s do a GET call and then take a look at the logs from our terminal: Notice the lines about the database connection being open/closed, the request data structure going to DynamoDB and then the response coming back, and finally the response data structure that is being returned by our API code. Removing the service At any point in time, if you want to remove the service from the cloud you can do the following: It will cleanup all the resources including IAM roles, the deployment bucket, the Lambda functions and will also delete the DynamoDB table. Summary To recap, we used Java to create a serverless REST API service, built it and then deployed it to AWS. We took a deep dive into the DAL code that handles the backend data mapping and access to the DynamoDB table. We also looked at the mapping between events, the API endpoints and the lambda function handlers in the service, all described intuitively in the serverless.yml file. By now, you should have an end-to-end implementation of a serverless REST API service written in Java and deployed to AWS. Hope you liked the post, and feel free to give me your feedback or ask any questions, in the comments below. Originally published at https://www.serverless.com.

  • maven-mvnd

    Apache Maven Daemon

    Install Apache Maven. After downloading and installing Apache Maven, add the apache-maven-x.x.x folder to the PATH environment variable.

  • Scout APM

    Less time debugging, more time building. Scout APM allows you to find and fix performance issues with no hassle. Now with error monitoring and external services monitoring, Scout is a developer's best friend when it comes to application development.

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts