AWS lambda无用的CRUD API与Jav​​a
#aws #serverless #lambda #java

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函数。

Image description

构建基本无服务器应用程序

在本教程中,我们将使用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

基本项目结构

Image description

  • 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 Deployment Output from AWS Lambda Serverless
AWS Lambda Function on the AWS dashboard
DynamoDB database created on AWS
Permissions were added on AWS Lambda to access DyanmoDB tables.

API测试

我们正在使用邮政集合来测试此API设置。您可以将Postman Collection与此link同步。

Image description

Image description

结论

在本文中,我们讨论了如何使用Java和DynamoDB构建无服务器API开发,并与AWS lambda一起部署。

所有这些示例和代码段的实现都可以在我们的GitHub存储库中找到。

快乐的编码。