AWS Lambda是Amazon Web Services(AWS)无服务器计算平台,它允许开发人员在不构建或管理服务器的情况下运行其代码。 Java是AWS Lambda支持的最受欢迎的编程语言之一。具有使用Java的能力,开发人员可以使用其现有技能和库在AWS Lambda平台上构建和部署无服务器应用程序。
Java开发人员可以通过创建和运行基于Java的AWS lambda功能来利用AWS lambda的灵活性和可扩展性。此过程涉及设置所需的环境,编写和测试代码以及在AWS Lambda平台上部署操作。在AWS lambda上使用Java,开发人员可以快速构建和部署无服务器的应用程序来处理任何流量。
在本文中,我们将解释如何在实际情况下使用以下技术堆栈构建CRUD API,
- Java 8
- aws lambda
- 无服务器
- dynamodb
REST API开发的需求
在本文中,我们将开发无服务器功能,以支持将作者数据集存储在DynamoDB表上的以下要求,并覆盖CRUD API函数。
构建基本无服务器应用程序
在本教程中,我们将使用serverless CLI在后期阶段创建应用程序并管理API基础架构。
使用Java 8运行时创建无服务器应用程序
目前,无服务器支持以下模板,
- aws-java gradle
- aws-java-maven
按照您的要求选择Maven或Gradle Java模板。我们将使用基于Gradle的Java应用程序。
$ serverless create --template aws-java-gradle --path aws-lambda-serverless-crud-java
基本项目结构
-
com.serverless包 - 在这里我们保留所有来源,这是该项目的基本包。
-
serverless.ymlâââ€â€,我们可以配置无服务器应用程序基础架构,API路径,资源,环境变量,权限等。基本上,无服务器CLI使用此YML文件来设置云形成指令对于此应用程序。
-
处理程序我们可以创建处理程序,并用API端点(可选)指向serverless.yml。基本上,处理程序充当无服务器API的起点。
为部署配置分布和其他配置 - 可选步骤
默认情况下,无服务器应用程序在hello.zip名称下创建构建/分布的分布。
我们可以更改它,并以我们喜欢的方式创建和命名最终分布。
在buildzip任务下以下面的方式更改build.gradle。
task buildZip(type: Zip) {
archiveBaseName = "aws-lambda-serverless-crud-java"
from compileJava
from processResources
into('lib') {
from configurations.runtimeClasspath
}
}
然后更改serverless.yml中的分布路径,
package:
artifact: build/distributions/aws-lambda-serverless-crud-java.zip
此外,我们可以配置我们将要部署此应用程序的区域和应用程序阶段。随时使用任何区域和舞台创建您的应用程序。
provider:
name: aws
runtime: java8
stage: development
region: us-west-2
DynamoDB表和从无服务器访问的权限
在此要求中,我们有两种配置数据库设置并创建权限的方法,这是一种手动方式和一种自动化方式,我们可以使用serverless.yml
轻松设置。让我们专注于可以通过serverless.yml配置的自动化方式,
首先,我们需要创建所需的DynamoDB表,并添加权限以从此无服务器应用程序访问该表。在这里,我们使用一个环境变量来设置数据库表,因为当我们在稍后阶段访问数据库时,这将使我们的生活更轻松。
provider:
name: aws
runtime: java8
stage: development
region: us-west-2
environment:
REGION: ${opt:region, self:provider.region}
AUTHOR_TABLE: javatodev-author-${opt:stage, self:provider.stage}
然后表定义,
resources:
Resources:
AuthorDynamoDBTable:
Type: "AWS::DynamoDB::Table"
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: "id"
AttributeType: "S"
KeySchema:
- AttributeName: "id"
KeyType: "HASH"
StreamSpecification:
StreamViewType: "NEW_AND_OLD_IMAGES"
TableName: ${self:provider.environment.AUTHOR_TABLE}
现在,我们创建了定义以在此应用程序上创建必要的Dynamo DB表。
然后,我们需要给出必要的权限以在应用程序API流程上使用这些表。
provider:
name: aws
runtime: java8
stage: development
region: us-west-2
# ENVIRONMENT VARIABLES
environment:
REGION: ${opt:region, self:provider.region}
AUTHOR_TABLE: javatodev-author-${opt:stage, self:provider.stage}
# IAM ROLES TO ACCESS DYNAMODB TABLES
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:BatchGetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- !GetAtt AuthorDynamoDBTable.Arn
在这里,我们需要显示正确的ARN来确定使用该应用程序创建的表。我们可以使用!getAtt authordynamodbtable.arn 。
API开发
现在,我们可以专注于开发API处理程序,并通过AWS Lambda无服务器通过HTTP API曝光。
DTO / UTIT和其他类
在此API中,我们使用一个单独的UTIT类,该类使用Jackson ObjectMapper将传入请求主体字符串转换为Java Pojo。
package com.serverless.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.serverless.exception.IncomingRequestParsingException;
public class RequestConversionUtil {
ObjectMapper objectMapper = new ObjectMapper();
public <T> T parseRequestBody(String requestBodyContent, Class<T> outPutClass) {
try {
return objectMapper.readValue(requestBodyContent, outPutClass);
} catch (JsonProcessingException e) {
throw new IncomingRequestParsingException();
}
}
}
另外,我们使用了2个主要模型类来从API中引入和输出数据。
package com.serverless.model;
public class AuthorDto {
private String id;
private String firstName;
private String lastName;
private String email;
private String identificationNumber;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getIdentificationNumber() {
return identificationNumber;
}
public void setIdentificationNumber(String identificationNumber) {
this.identificationNumber = identificationNumber;
}
@Override public String toString() {
return "AuthorDto{" +
"id='" + id + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", identificationNumber='" + identificationNumber + '\'' +
'}';
}
}
package com.serverless.model;
public class CommonAPIResponse {
private String message;
public CommonAPIResponse(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
基础API端点
只是带有传入请求的简单JSON响应。
functions:
base_api:
handler: com.serverless.Handler
events:
- httpApi:
path: /
method: get
package com.serverless;
import java.util.Collections;
import java.util.Map;
import org.apache.log4j.Logger;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class Handler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
private static final Logger LOG = Logger.getLogger(Handler.class);
@Override
public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
Response responseBody = new Response("Go Serverless v1.x! Your function executed successfully!", input);
return ApiGatewayResponse.builder()
.setStatusCode(200)
.setObjectBody(responseBody)
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.build();
}
}
作者创建API端点
在这里,我们将创建一个API端点,该API端点支持http的http方法,该方法允许我们带来带有传入请求的请求机体。
author_registration:
handler: com.serverless.author.RegistrationHandler
events:
- httpApi:
path: /authors/registration
method: post
package com.serverless.author;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.serverless.ApiGatewayResponse;
import com.serverless.Handler;
import com.serverless.model.AuthorDto;
import com.serverless.model.CommonAPIResponse;
import com.serverless.util.RequestConversionUtil;
import org.apache.log4j.Logger;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class RegistrationHandler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
private static final Logger LOG = Logger.getLogger(RegistrationHandler.class);
private AmazonDynamoDB amazonDynamoDB;
private String AUTHOR_DB_TABLE = System.getenv("AUTHOR_TABLE");
private Regions REGION = Regions.fromName(System.getenv("REGION"));
@Override public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
RequestConversionUtil requestConversionUtil = new RequestConversionUtil();
AuthorDto request = requestConversionUtil.parseRequestBody(input.get("body").toString(), AuthorDto.class);
LOG.info("Incoming author registration request "+request.toString());
this.initDynamoDbClient();
persistData(request);
return ApiGatewayResponse.builder()
.setStatusCode(201)
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.setObjectBody(new CommonAPIResponse("Author registration successfully completed."))
.build();
}
private String persistData(AuthorDto request) throws ConditionalCheckFailedException {
String user_id = UUID.randomUUID().toString();
Map<String, AttributeValue> attributesMap = new HashMap<>();
attributesMap.put("id", new AttributeValue(user_id));
attributesMap.put("firstName", new AttributeValue(request.getFirstName()));
attributesMap.put("lastName", new AttributeValue(request.getLastName()));
attributesMap.put("email", new AttributeValue(request.getEmail()));
attributesMap.put("identification_number", new AttributeValue(request.getIdentificationNumber()));
amazonDynamoDB.putItem(AUTHOR_DB_TABLE, attributesMap);
return user_id;
}
private void initDynamoDbClient() {
this.amazonDynamoDB = AmazonDynamoDBClientBuilder.standard()
.withRegion(REGION)
.build();
}
}
AWS Lambda Java中的阅读环境变量
在此API中,我们将从环境变量中读取区域和表名。如果我们不得不在将来更改桌子名称,这将使开发人员的生活更加轻松。
private String AUTHOR_DB_TABLE = System.getenv("AUTHOR_TABLE");
发送AWS Lambda Java的JSON回应
默认情况下,使用普通/文本内容类型,所有响应都来自AWS lambda API。由于我们需要将所有响应作为JSON发送给消费者,因此我们必须在所有API响应中设置Content-type:应用程序/JSON标题。
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
使用Findall和Findbyid阅读作者API端点
author_reads:
handler: com.serverless.author.ReadHandler
events:
- httpApi:
path: /authors
method: get
package com.serverless.author;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
import com.amazonaws.services.dynamodbv2.model.GetItemResult;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.serverless.ApiGatewayResponse;
import com.serverless.model.AuthorDto;
import com.serverless.model.CommonAPIResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ReadHandler implements RequestHandler<APIGatewayProxyRequestEvent, ApiGatewayResponse> {
private AmazonDynamoDB amazonDynamoDB;
private String AUTHOR_DB_TABLE = System.getenv("AUTHOR_TABLE");
private Regions REGION = Regions.fromName(System.getenv("REGION"));
@Override public ApiGatewayResponse handleRequest(APIGatewayProxyRequestEvent input, Context context) {
this.initDynamoDbClient();
Map<String, String> queryParams = input.getQueryStringParameters();
if (queryParams != null && queryParams.containsKey("findAll") && Boolean.parseBoolean(queryParams.get("findAll"))) {
//Find All
Map<String, AttributeValue> lastKeyEvaluated = null;
List<AuthorDto> authorDtos = new ArrayList<>();
do {
ScanRequest scanRequest = new ScanRequest()
.withTableName(AUTHOR_DB_TABLE)
.withLimit(10)
.withExclusiveStartKey(lastKeyEvaluated);
ScanResult result = amazonDynamoDB.scan(scanRequest);
for (Map<String, AttributeValue> item : result.getItems()) {
authorDtos.add(mapToDto(item));
}
lastKeyEvaluated = result.getLastEvaluatedKey();
} while (lastKeyEvaluated != null);
return ApiGatewayResponse.builder()
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.setObjectBody(authorDtos).setStatusCode(200).build();
} else if (queryParams!= null && queryParams.containsKey("id") && queryParams.get("id") != null) {
//Find by id
Map<String, AttributeValue> attributesMap = new HashMap<>();
attributesMap.put("id", new AttributeValue(queryParams.get("id")));
GetItemRequest getItemRequest = new GetItemRequest().withTableName(AUTHOR_DB_TABLE)
.withKey(attributesMap);
GetItemResult item = amazonDynamoDB.getItem(getItemRequest);
return ApiGatewayResponse.builder()
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.setObjectBody(mapToDto(item.getItem())).setStatusCode(200).build();
}
return ApiGatewayResponse.builder()
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.setObjectBody(new CommonAPIResponse("No data found under given query"))
.setStatusCode(200).build();
}
private AuthorDto mapToDto(Map<String, AttributeValue> item) {
AuthorDto authorDto = new AuthorDto();
authorDto.setId(item.get("id").getS());
authorDto.setEmail(item.get("email").getS());
authorDto.setFirstName(item.get("firstName").getS());
authorDto.setLastName(item.get("lastName").getS());
authorDto.setIdentificationNumber(item.get("identification_number").getS());
return authorDto;
}
private void initDynamoDbClient() {
this.amazonDynamoDB = AmazonDynamoDBClientBuilder.standard()
.withRegion(REGION)
.build();
}
}
使用AWS Lambda Java使用查询参数
在这里,我们需要带一个查询参数,以确定用户要求从DB读取的内容以在Findall和FindbyId之间切换。
在这种情况下,我们可以使用ApigatewayProxyrequestevent与AWS Core库捆绑在一起,从传入的请求轻松捕获这些参数。
Map<String, String> queryParams = input.getQueryStringParameters();
queryParams.get("findAll");
更新作者端点
author_update:
handler: com.serverless.author.UpdateHandler
events:
- httpApi:
path: /authors/{id}
method: patch
在这里,我们将请求机体和作者ID作为路径参数。我们可以像阅读端点一样使用ApigatewayProxyrequestevent捕获两者。
package com.serverless.author;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.model.AttributeAction;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodbv2.model.UpdateItemRequest;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.serverless.ApiGatewayResponse;
import com.serverless.model.AuthorDto;
import com.serverless.model.CommonAPIResponse;
import com.serverless.util.RequestConversionUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class UpdateHandler implements RequestHandler<APIGatewayProxyRequestEvent, ApiGatewayResponse> {
private AmazonDynamoDB amazonDynamoDB;
private String AUTHOR_DB_TABLE = System.getenv("AUTHOR_TABLE");
private Regions REGION = Regions.fromName(System.getenv("REGION"));
@Override public ApiGatewayResponse handleRequest(APIGatewayProxyRequestEvent input, Context context) {
initDynamoDbClient();
RequestConversionUtil requestConversionUtil = new RequestConversionUtil();
AuthorDto request = requestConversionUtil.parseRequestBody(input.getBody(), AuthorDto.class);
Map<String, AttributeValue> keyMap = new HashMap<>();
keyMap.put("id", new AttributeValue(input.getPathParameters().get("id")));
UpdateItemRequest updateItemRequest = new UpdateItemRequest()
.withTableName(AUTHOR_DB_TABLE)
.addKeyEntry("id", new AttributeValue(input.getPathParameters().get("id")))
.addAttributeUpdatesEntry("firstName",
new AttributeValueUpdate(
new AttributeValue(request.getFirstName()),
AttributeAction.PUT))
.addAttributeUpdatesEntry("lastName",
new AttributeValueUpdate(
new AttributeValue(request.getLastName()),
AttributeAction.PUT))
.addAttributeUpdatesEntry("email",
new AttributeValueUpdate(
new AttributeValue(request.getEmail()),
AttributeAction.PUT))
.addAttributeUpdatesEntry("identification_number",
new AttributeValueUpdate(
new AttributeValue(request.getIdentificationNumber()),
AttributeAction.PUT));
amazonDynamoDB.updateItem(updateItemRequest);
return ApiGatewayResponse.builder()
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.setObjectBody(new CommonAPIResponse("Author update successfully completed")).build();
}
private void initDynamoDbClient() {
this.amazonDynamoDB = AmazonDynamoDBClientBuilder.standard()
.withRegion(REGION)
.build();
}
}
删除作者API端点删除
author_delete:
handler: com.serverless.author.DeleteHandler
events:
- httpApi:
path: /authors/{id}
method: delete
package com.serverless.author;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.serverless.ApiGatewayResponse;
import com.serverless.model.CommonAPIResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class DeleteHandler implements RequestHandler<APIGatewayProxyRequestEvent, ApiGatewayResponse> {
private AmazonDynamoDB amazonDynamoDB;
private String AUTHOR_DB_TABLE = System.getenv("AUTHOR_TABLE");
private Regions REGION = Regions.fromName(System.getenv("REGION"));
@Override public ApiGatewayResponse handleRequest(APIGatewayProxyRequestEvent input, Context context) {
initDynamoDbClient();
Map<String, AttributeValue> keyMap = new HashMap<>();
keyMap.put("id", new AttributeValue(input.getPathParameters().get("id")));
DeleteItemRequest request = new DeleteItemRequest()
.withTableName(AUTHOR_DB_TABLE)
.withKey(keyMap);
amazonDynamoDB.deleteItem(request);
return ApiGatewayResponse.builder()
.setHeaders(Collections.singletonMap("Content-Type", "application/json"))
.setObjectBody(new CommonAPIResponse("Author deletion successfully completed")).build();
}
private void initDynamoDbClient() {
this.amazonDynamoDB = AmazonDynamoDBClientBuilder.standard()
.withRegion(REGION)
.build();
}
}
最后,已完成的server.yml应该看起来像下面,请确保您已经以正确的级别编写了配置yml。
service: aws-lambda-serverless-crud-java
frameworkVersion: '3'
provider:
name: aws
runtime: java8
stage: development
region: us-west-2
# ENVIRONMENT VARIABLES
environment:
REGION: ${opt:region, self:provider.region}
AUTHOR_TABLE: javatodev-author-${opt:stage, self:provider.stage}
# IAM ROLES TO ACCESS DYNAMODB TABLES
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:BatchGetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- !GetAtt AuthorDynamoDBTable.Arn
resources:
Resources:
AuthorDynamoDBTable:
Type: "AWS::DynamoDB::Table"
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: "id"
AttributeType: "S"
KeySchema:
- AttributeName: "id"
KeyType: "HASH"
StreamSpecification:
StreamViewType: "NEW_AND_OLD_IMAGES"
TableName: ${self:provider.environment.AUTHOR_TABLE}
package:
artifact: build/distributions/aws-serverless-crud-java.zip
functions:
base_api:
handler: com.serverless.Handler
events:
- httpApi:
path: /
method: get
author_registration:
handler: com.serverless.author.RegistrationHandler
events:
- httpApi:
path: /authors/registration
method: post
author_reads:
handler: com.serverless.author.ReadHandler
events:
- httpApi:
path: /authors
method: get
author_update:
handler: com.serverless.author.UpdateHandler
events:
- httpApi:
path: /authors/{id}
method: patch
author_delete:
handler: com.serverless.author.DeleteHandler
events:
- httpApi:
path: /authors/{id}
method: delete
现在都完成了,我们已经为整个API编写了必要的API端点。我们可以将API部署到AWS lambda。
$ serverless deploy
API测试
我们正在使用邮政集合来测试此API设置。您可以将Postman Collection与此link同步。
结论
在本文中,我们讨论了如何使用Java和DynamoDB构建无服务器API开发,并与AWS lambda一起部署。
所有这些示例和代码段的实现都可以在我们的GitHub存储库中找到。
快乐的编码。