Java - Building Serverless Restful Microservices with AWS Lambda and API Gateway

Service oriented architecture(SOA) separated the business logic from presentation layer, then comes IoT(Internet of Things) which need the services to be served to multiple clients such as web, Mobile and middle-ware applications. Also, the Cloud Computing Architecture made application developers life easier by providing highly scalable and reliable infrastructure.

But when the business grows, business complexity also grows and the services exposed also becomes non-maintainable. Your code becomes too complex; continuous delivery, testing, scaling - all becomes difficult. Micro-service architecture solves this problem by breaking down business functionalities into small, highly scalable, loosely coupled services called micro-services which runs in parallel.

Since the services are completely independent, the code will be clean, testable and maintainable. Also, the developers can independently work on the services, can independently test the features, can do continuous delivery without affecting other running services(must be backward compatible). When it comes to scalability, micro-service architecture gives you complete freedom in scaling individual services on increase in load.

No doubt, micro-service architecture is the future of enterprise software development. You can build micro-services in any technology you like and deploy them on your favorite application server running on private or public cloud(aws, azure, google cloud etc.). Also some cloud service providers provides Platform as a Service(PaaS) where they will take care of the capacity provisioning, load balancing, auto-scaling, and application health monitoring(e.g. Amazon Elastic Beanstalk, Google App Engine etc.). Amazon went ahead and provided one more platform where you don't even have to think of infrastructure or platform at all, all you need to do is, write your code and upload to aws! Yes, aws Lambda makes it possible to run a piece of code driven by events or by HTTP requests. Lambda takes care of provisioning and managing the servers and also takes care of auto-scaling, monitoring and logging.

In this article, you will learn how to build highly scalable micro-services using Java 8, AWS Lambda and AWS API Gateway. You should have an active AWS account, basic knowledge on various AWS services, understanding of RESTful(REpresentational State Transfer) services and of course basic Java coding skills.

DynamoDB Database Setup



First we will setup a DynamoDB table where we can insert, update delete records. Go to DynamoDB section in aws console and create a table named "product". Add "sku" as primary key(partition key), leave all other settings as default and click on "Create". Now let's insert a new item, for this, go to Items tab and click on "Create Item". Select "Text" from the drop-down and paste the below json content in the area provided, then click on "Save".

{
  "Sku": "SKU00001",
  "Name": "Apple iPhone 6",
  "ImageUrl": "http://www.appleg.com/images/iphone6.png",
  "ShortDescription": "Short Description",
  "LongDescription": "Long Description.",
  "Price": "699.99"
}

Writing business logic


We will simply read product details from DynamoDB and return the result back to the caller. Create a maven project and add dependencies for lambda and dynamodb, your pom.xml will like like this:

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.texient</groupId>
  <artifactId>ecommerce</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>ecommerce</name>
   <dependencies>
   <dependency>
  <groupId>com.amazonaws</groupId>
  <artifactId>aws-lambda-java-core</artifactId>
  <version>1.1.0</version>
 </dependency>
 <dependency>
  <groupId>com.amazonaws</groupId>
  <artifactId>aws-java-sdk-dynamodb</artifactId>
  <version>1.10.54</version>
 </dependency>
 <dependency>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>2.4.3</version>
 </dependency>
  </dependencies>
</project>

Now, we need the domain class, say Product.java as below:

package com.texient.ecommerce.model;

public class Product {
 
 private String sku;
 private String name;
 private String shortDescription;
 private String longDescription;
 private String imageUrl;
 private String price;
 
 public String getSku() {
  return sku;
 }
 public void setSku(String sku) {
  this.sku = sku;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getShortDescription() {
  return shortDescription;
 }
 public void setShortDescription(String shortDescription) {
  this.shortDescription = shortDescription;
 }
 public String getLongDescription() {
  return longDescription;
 }
 public void setLongDescription(String longDescription) {
  this.longDescription = longDescription;
 }
 public String getImageUrl() {
  return imageUrl;
 }
 public void setImageUrl(String imageUrl) {
  this.imageUrl = imageUrl;
 }
 public String getPrice() {
  return price;
 }
 public void setPrice(String price) {
  this.price = price;
 }

}

Let us implement the business logic in a lambda request handler class as follows:

package com.texient.ecommerce.service;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.texient.ecommerce.dynamodb.ProductDelegate;
import com.texient.ecommerce.model.Product;

public class GetProductImpl implements RequestHandler{
 
 public Product handleRequest(Product product, Context context){
           context.getLogger().log("Input: " + product.getSku());
           product = ProductDelegate.get(product.getSku());
           if(product!=null) {
         context.getLogger().log("Returning product details");
         return product;
           }else{
         throw new RuntimeException("404");
           }
    }
 
}


Creating a Lambda Function



Let's create a Lambda function which will execute the handler method we implemented earlier via API Gateway. In REST world it is a GET endpoint, say /product/{sku} which will return the product details of {sku}. Go to Lambda tab in aws console and click on "Create a Lambda Function". Select "microservice-http-endpoint" blueprint, provide name as "GetProduct" and select "Runtime" as "Java 8". Provide S3 url(or upload jar file) for your Lambda function code, enter handler as "com.texient.ecommerce.service.GetProductImpl::handleRequest" and select a role which has complete access to S3, DynamoDB and Lambda. Leave all other fields as default and click on "Next".

On next page we will setup the API endpoint, provide API name as "ProductService", Resource anme as "/product", method as "GET", stage as "Prod" and security as "Open"(for time being). Click "Next", review the details and click "Finish". This will create a new Lambda function "GetProduct" and an end point which points to the newly created Lambda function.


Setting up API Gateway


If you open the API Gateway tab in aws console, you will see an API already created for you by the "microservice-http-endpoint" blueprint. Click on /product and then Create Resource button, provide "Sku" as resource name and {sku} as path param. Create a "GET" method under /product/{sku}, click on it and then in the Integration Request, select integration type as Lambda Function and select the lambda function you created in the earlier step. Add a new mapping template "application/json", then fill the template with the below json:

{
    "sku" : "$input.params('sku')"
}

Also you can use the below shortcut to map all your query parameters to Lambda parameter "query":
{
    "query": "$input.params().querystring"
}

We are all set to test the API end point. On the left side you will see a "Test" button, click on it and provide "SKU00001" for sku and hit "Test" button. A console will appear on the right with the log, where you can see the response body and response status. If you have followed the steps as mentioned above, you will get a 200 response with the product details in the response. In the log section you will see the debug log, input to the Lambda function, and the transformed output.

Publish the API


So we have an API endpoint pointing to the Lambda function; to invoke it from a REST client, you need to deploy the API. On top left there is a "Deploy API" button, hit on it, the API is deployed and you will get an endpoint similar to https://blabla.execute-api.us-west-2.amazonaws.com/prod/. You can now do a GET for sku SKU00001 by invoking the url https://blabla.execute-api.us-west-2.amazonaws.com/prod/product/SKU00001.


Conclusion


We implemented and deployed a highly scalable RESTful micro-service with AWS Lambda and API Gateway without even thinking of application servers nor on load balancers. AWS takes care of provisioning of compute units and auto-scaling. Also, aws provides options to cache the responses, which heavily decreases the processing time and thus response time. In our test, we noticed initial request taking more response time, subsequent requests are served incredibly fast, could be due to provisioning of resources. Also you will have to do an error mapping to return correct HTTP status code, which is completely manual. Apart from these, we feel API Gateway + Lambda stack is a super simple but extremely powerful tool for building enterprise level micro-services. For existing services, you can simplify migration by importing the Swagger documentation, API Gateway creates all end points and models defined in the documentation, you just need to create Lambda functions and associate them with the end points.












4 Comments

Did you enjoy this post? Why not leave a comment below and continue the conversation, or subscribe to our feed and get articles like this delivered automatically to your feed reader? Like our Facebook Page.

  1. Build aws cloud applications faster with Macbook.

    ReplyDelete
  2. You are missing the listing for ProductDelegate and GetProductImpl will not compile

    ReplyDelete
  3. ProductDelegate code is missing. Will you please provide it to complete the tutorial.

    ReplyDelete
    Replies
    1. ProductDelegate is just a DynamoDB CRUD implementation, refer http://docs.aws.amazon.com/amazondynamodb/latest/gettingstartedguide/GettingStarted.Java.03.html

      Delete
Post a Comment
Previous Post Next Post