AWS for Java Developers Part 4
AWS Lambda
- AWS Lambda is a serverless and event-driven compute service. It allows you to upload a piece of source code to execute against a valid event. The uploaded piece of code is called a Lambda function.
-
AWS Lambda functions includes source code along with all dependencies.
-
Each Lambda function has its own configuration informations, such as runtime, environment variables,
handler, IAM role, tag(s), memory, timeout, VPC, and many other details that are defined at the time of creating.
-
Lambda function can be configured to execute in between 1 to 900 seconds. Lambda function execution time is called timeout. If the Lambda function is running after the defined timeout, it is automatically terminated.
While creating a Lambda function along with memory, here are few more parameters that need to be defined:
-
Maximum execution time (timeout):
The maximum it can be 15 minutes. It helps to prevent the Lambda function from running indefinitely. When timeout has been reached, the Lambda function execution terminates.
-
IAM role (execution role):
Lambda can assume an IAM role at the time of execution. Based on the privileges granted to the IAM role, the Lambda function can inherit the privileges for executing the function.
-
Handler name :
It refers to the method name to be used by AWS Lambda to start the execution. AWS Lambda passes an event information that triggers the invocation as a parameter to the handler method.
Lambda Function Invocation Types
-
AWS Lambda supports two invocation methods: synchronous and asynchronous.
-
The invocation type can be only specified at the time of manually executing a Lambda function.
-
This Lambda function execution is called on-demand invocation.
-
On-demand invocation is done by the invoke operation. It allows you to specify the invocation type, synchronous or asynchronous.
Writing a Lambda Function
- AWS lambda supports Node.js, Java, Python and C#
-
Irrespective of the programming language used to write the AWS Lambda function there is a common pattern to write a code for a Lambda function. It includes the following concept.
-
Handler
-
The Context Object
-
Logging
-
Lambda function Handler
- The general syntax of handler function is as follows :
outputType handler-name(inputType input, Context context) {
...}
- Here is a list of components that are part of the syntax :
- inputType : This can be an event data or custom input that is provided as a string or any custom data object. To successfully invoke this handler, the Lambda function must be invoked with the input data that can be serialized into thedefined data type of the input parameter.
- outputType : When the Lambda function is invoked synchronously using the RequestResponse invocation type, it is possible to return the output of the Lambda function using the valid and supported data type.
inputType and outputType can be one of the following:
-
Predefined AWS event types: They are defined in the aws-lambda-java-events library.
-
Plain Old Java Object (POJO) class: It allows to create own POJO class. Lambda function automatically serializes, deserializes input, and output on the POJO type
or JSON.
-
Primitive Java types: It supports primitive Java types such as String or int.
Simple Lambda
build.gradle
plugins {
id 'java'
}
group 'com.lambda.demo'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
jar {
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
dependencies {
compile group: 'com.amazonaws', name: 'aws-lambda-java-core', version: '1.2.0'
compile 'com.amazonaws:aws-lambda-java-events:2.2.6'
compile group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.659'
compile group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.659'
compile group: 'com.amazonaws', name: 'aws-java-sdk-lambda', version: '1.11.118'
}
package com.lambda.demo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.Map;
public class LambdaRequestHandler implements RequestHandler<Map<String,Object>,String> {
@Override
public String handleRequest(Map<String, Object> input, Context context) {
return "Hello" + input.get("name");
}
}
Java Code
Go to AWS lambda service and click on Create Function
Now Provide lambda configuration
- Select author from scratch option
- Provide the function name
- Provide Java 8 for Runtime
Select Create a new role with basic Lambda Permission in Execution role and Now click on Create Function
Please make sure that following Policies are attached in your Role assigned to lambda
Click on Upload button and Upload the Jar of the Java file which we have created earlier and in the handler specify package with Class and method name in the format specified
Select the Execution Role and in basic Setting provide CPU proportion and Time out
Select the VPC settings and Click on Test Button to provide the test case
Specify the event name and Parameter for testing and click on Create button
Now hit the Test button and see you lambda executing
Invoking lambda function from Java Code
package com.lambda.demo;
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;
import java.io.UnsupportedEncodingException;
public class InvokingLambdaFunctions {
public static void main(String[] args) throws UnsupportedEncodingException {
AWSLambda client = AWSLambdaClientBuilder
.standard()
.withRegion(Regions.AP_SOUTH_1)
.build();
InvokeRequest req = new InvokeRequest()
.withFunctionName("BasicLambda")
.withPayload("{ \"name\":\"peter\" }"); // optional
InvokeResult invokeResult = client.invoke(req);
System.out.println(invokeResult);
System.out.println(invokeResult.getPayload());
String converted = new String(invokeResult.getPayload().array(), "UTF-8");
System.out.println(converted);
}
}
Exercise 1
- Create a Aws Lambda and upload it to Amazon AWS lambda Service
- Create a Test for the uploaded lambda
- Run the test and see the logs in cloudwatch
- Invoke the lambda function from Java code.
Create a S3 bucket for lambda invocation
package com.lambda.demo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
public class LambdaS3RequestHandle implements RequestHandler<S3Event, String> {
@Override
public String handleRequest(S3Event event, Context context) {
LambdaLogger lambdaLogger = context.getLogger();
lambdaLogger.log("Received event" + event);
// Get the object from the event and show its content type
String bucket = event.getRecords().get(0).getS3().getBucket().getName();
String key = event.getRecords().get(0).getS3().getObject().getKey();
lambdaLogger.log("bucket >>>>" + bucket);
lambdaLogger.log("key >>>>" + key);
return "S3 Uploaded...";
}
}
Create a lambda jar with following class
Create a new function
Add Trigger
Select S3 trigger
Fill the details and click on add
You will see that S3 trigger added
Upload the lambda to catch S3 Object create event
Configure VPC for the lambda
Upload file to S3 bucket
In the cloudwatch you can check the logs for the lambda function
You can see the logs for the S3 bucket upload from the lambda
Java Code to Capture SQS Event using lambda
package com.lambda.demo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
public class LambdaSQSRequestHandler implements RequestHandler<SQSEvent,String> {
@Override
public String handleRequest(SQSEvent event, Context context) {
LambdaLogger lambdaLogger = context.getLogger();
for(SQSEvent.SQSMessage msg : event.getRecords()){
lambdaLogger.log(new String(msg.getBody()));
}
return event.toString();
}
}
Create an SQS to Test with Lambda
Create a Lambda Function and configure VPC and assign a role to it
Upload the Lambda which contains SQS event handling
Go to the SQS right click and choose Configure Trigger for Lambda Function
Select the Lambda which needs to be triggered from SQS
Send a message to SQS
You can check the logs of lambda triggered from cloudwatch
package com.lambda.demo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
public class LambdaSNSRequestHandler implements RequestHandler<SNSEvent, String> {
@Override
public String handleRequest(SNSEvent input, Context context) {
LambdaLogger lambdaLogger = context.getLogger();
lambdaLogger.log(input.getRecords().get(0).getSNS().getMessage());
return input.toString();
}
}
Java Code for SNS Event
Create SNS topic to Trigger Lambda
Create a lambda Function which need to be triggered by SNS, Specify VPC, upload the jar and enter the Handler
Create SNS Subscription for Lambda
Publish the Message from SNS
Now check the logs in Cloudwatch
Exercise 2
- Create a lambda which triggers when some object is uploaded on the S3.
- Create a lambda which consumes the items from SQS
- Create a lambda which is triggered when data is pushed in SNS
API Gateway
- Amazon API Gateway is a fully managed service that makes it easy for developers to create, maintain and publish APIs.
- API Gateway has no minimum fees or startup costs. You pay only for the API calls you receive and the amount of data transferred out.
Creating API using API Gateway
- Go to the API Gateway Service
Click on create API button
Fill the details for your API and Hit Create API
Go to the Action button and click on create resource
Fill the name of the resource, enable API Gateway for cors and Create Resource
Select the resource, Go to the Actions and Create Method
Select the method type for the resource
Now create a lambda with the code below
package com.lambda.demo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class LambdaAPIEmployeeGetHandler implements RequestHandler<EmployeeRequest, String> {
@Override
public String handleRequest(EmployeeRequest input, Context context) {
LambdaLogger lambdaLogger = context.getLogger();
lambdaLogger.log(">>>>>>>>>>>>>>>>>>>>>>>"+input.toString());
return "Request Accepted by Lambda";
}
}
package com.lambda.demo;
public class EmployeeRequest {
private String httpMethod;
// Getter, Setter and toString
}
Select the lambda you need to trigger for Get call and Click on Save
Click on ok to add API Gateway Permission to lambda
Click on the Integration Request
Go to the Mapping Templates, click on Add mapping template and enter application/json in text box
Use the velocity script for template mapping to capture request variables and click on save
Now go to the Action and Deploy the API
Create a new Deployment Stage and Click on Deploy
Now copy the Invoke URL
Hit the invoke url after suffix resource name
You can check the logs in Cloudwatch
Creating a POST request with DynamoDB
Click on Create table
Enter the name of the table and primary key and click on Create button
Create a Lambda for Creating Employee
package com.lambda.demo;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.PrimaryKey;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class CreateEmployee implements RequestHandler<EmployeeRequest, Item> {
@Override
public Item handleRequest(EmployeeRequest input, Context context) {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.AP_SOUTH_1)
.build();
LambdaLogger lambdaLogger = context.getLogger();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("employee");
Employee paramEmployee = input.getEmployee();
try{
lambdaLogger.log("paramEmployee>>"+paramEmployee);
Item item = new Item()
.withPrimaryKey("id", paramEmployee.getId())
.withString("name", paramEmployee.getName())
.withString("age", paramEmployee.getAge());
lambdaLogger.log("Trying to save the Item");
table.putItem(item);
lambdaLogger.log("Saved Item");
}catch (Exception ex){
lambdaLogger.log("Exception :: "+ex.getMessage());
}
return table.getItem(new PrimaryKey("id",paramEmployee.getId()));
}
}
Employee POJO
package com.lambda.demo;
public class Employee {
private String id;
private String name;
private String age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
Employee Request
package com.lambda.demo;
public class EmployeeRequest {
private String httpMethod;
private Employee employee;
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public String getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(String httpMethod) {
this.httpMethod = httpMethod;
}
@Override
public String toString() {
return "EmployeeRequest{" +
"httpMethod='" + httpMethod + '\'' +
", employee=" + employee +
'}';
}
}
Upload the Lambda for creating Employee
Create POST method employee
Click ok to give API Gateway permission to invoke Lambda
Go to the Integration Request
In the mapping template add application/json and the velocity script
#set($inputRoot = $input.path('$'))
{
"httpMethod" : "$context.httpMethod",
"employee" : $input.json('$')
}
Deploy the API
Deploy Emp
Now hit the Post request for Employee
You can check the entry in Dynamo DB for Employee Created
Cloudwatch logs for Object created
Error Handling For POST request
Go to the Models and Create a new Model as shown below
Go to the Method Request Section of POST Method
Select the Values of Request Validator, HTTP Request Harders and Request Body as shown below
In the Gateway Response Section Change the Velocity Script for Response Template as shown below for Bad Request Body
Fetch the Data from Dynamo DB
package com.lambda.demo;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.List;
public class LambdaAPIEmployeeGetHandler implements RequestHandler<EmployeeRequest, List<Employee>> {
@Override
public List<Employee> handleRequest(EmployeeRequest input, Context context) {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.AP_SOUTH_1)
.build();
LambdaLogger lambdaLogger = context.getLogger();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("employee");
DynamoDBMapper mapper = new DynamoDBMapper(client);
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
List<Employee> scanResult = mapper.scan(Employee.class, scanExpression);
lambdaLogger.log(scanResult.toString());
return scanResult;
}
}
package com.lambda.demo;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
@DynamoDBTable(tableName="employee")
public class Employee {
private String id;
private String name;
private String age;
@DynamoDBHashKey(attributeName="id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
Introduce @DynamoDBTable and
Deploy the Latest Jar for GetEmployee
Create a new resource for Path variable employee id
Specify the Path Variable id in Resource name and click on Create Resource
Introduce a Get method for employee/{id}
package com.lambda.demo;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class GetEmployee implements RequestHandler<EmployeeRequest, Employee> {
@Override
public Employee handleRequest(EmployeeRequest input, Context context) {
LambdaLogger lambdaLogger = context.getLogger();
AmazonDynamoDB client = AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.AP_SOUTH_1)
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("employee");
DynamoDBMapper mapper = new DynamoDBMapper(client);
Employee employee = mapper.load(Employee.class, input.getEmpId(),
new DynamoDBMapperConfig(DynamoDBMapperConfig.ConsistentReads.CONSISTENT));
lambdaLogger.log(employee.toString());
return employee;
}
}
Create and upload the lambda to Get One Employee
Specify the Lambda function to be triggered
Add Permission to the lambda Function
Go to the integration request
Under the integration Request select the Mapping Template
#set($inputRoot = $input.path('$'))
{
"httpMethod":"$context.httpMethod",
"empId":"$input.params('id')",
"name": "$input.params().querystring.get('name')"
}
Script to generate Template
Deploying the API
Now hit the employee API to get one employee
Logs from Cloudwatch
Create a Plan and Click on Next
Select the API and Stage
Hit Next
Add the Employee API in the plan
Securing the API
Go to API Key and Select Create API Key
Enter the name of the key and Click on save
Enter the name of the plan for the Key
Click on Show to get the Key
Go to the method request and set API Key Required to true
Deploy the API
Hit the API without Token
Hitting the API with response
Exercise 3
- Create a Restful API for Student (id, name, age) to create the employee
- Create a Restful API to read all the records
- Create a Restful API to read the record on the basis on id.
- Secure the API with API TOKEN
Elastic Beanstalk
-
Traditionally, deploying a web application on AWS may have required spending time
-
selecting appropriate AWS services such as EC2, ELB, Auto Scaling, and so on, and creating and configuring an AWS resource from scratch to host a web application can be quite a pain.
-
It could be difficult for developers to build the infrastructure, configure the OS, install the required dependencies, and deploy the web services.
-
AWS Elastic Beanstalk removes the need to
manually build an infrastructure for the developer and makes it possible for them to quickly deploy and manage a web application on AWS of any scale.
Create a Spring Boot Project
Create a dummy Controller and Build the war file
Go to Elastic Bean Stack Service and Click on Get Started
Enter the Application name and platform, upload the spring boot war and then click on Create application
Select the war, click on upload and click on Create Application
You will start getting the logs now
You can check the status of your application in Cloudformation
You can check the various resouces created by Elastic beanstalk
Elastic IP
EC2
Now our Application is Deployed via elastic beanstalk. Url displayed at the top of this screen will be used to access the application
Hit the Url on the browser and you can see the result
Create an RDS instance
Introduce Spring Data JPA in Spring Boot Application
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
id 'war'
}
group = 'com.spring.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-web'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
spring.datasource.url=jdbc:mysql://mydb.cmetoyhg7bms.ap-south-1.rds.amazonaws.com:3306/mydb
spring.datasource.username=admin
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
application.properties
Employee Entity
package com.spring.demo.elasticbeanstalk.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer age;
public Employee() {
}
public Employee(String name, Integer age) {
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
Employee Repository
package com.spring.demo.elasticbeanstalk.repository;
import com.spring.demo.elasticbeanstalk.entity.Employee;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
}
package com.spring.demo.elasticbeanstalk.event;
import com.spring.demo.elasticbeanstalk.entity.Employee;
import com.spring.demo.elasticbeanstalk.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class Bootstrap implements CommandLineRunner {
@Autowired
EmployeeRepository employeeRepository;
@Override
public void run(String... args) throws Exception {
employeeRepository.save(new Employee("Peter",29));
System.out.println(employeeRepository.count());
}
}
build a new jar and upload the new version to the elastic beanstalk application
Select the war and click on deploy
You can check the logs of the deployed application
You can delete the application as shown below. It will also delete all the associated resources created by application
AWS for Java Developers Part 4
By Pulkit Pushkarna
AWS for Java Developers Part 4
- 1,012