AWS Lambda + API Gateway + DynamoDB 实战

风小楼WindJack教程web大约 8 分钟

AWS Serverless搭建Tensorflow/TFlite推理服务一文中,我们成功创建了一个基于tensorflow的推理服务。为了能把服务真正地用起来,我们还需提供对外访问的http api接口,以及访问数据库的能力。这就轮到API Gateway以及DynamoDB出场了。

使用SAM CLI快速创建应用模版

SAM(AWS 无服务器应用程序模型)是一个用于构建Serverless应用的开源框架。SAM CLI 是SAM的命令行工具。关于它们更详细的介绍及安装方式在实战1-利用sam-cli在aws-lambda上部署tensorflow一文中已经提到过了,这里不过多介绍。

首先我们通过sam init 命令初始化一个包含AWS Lambda、API Gateway、DynamoDB资源的应用模版。

  • 输入sam init ,选择"AWS Quick Start Templates"
  • 询问选择的模版,选择"Serverless API"
  • 询问选择的runtime,这里选择nodejs16.x
  • 询问是否使用X-Ray追踪应用性能,选择y
  • 输入project name

完成后我们将有一个quick start的模版

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: nodejs16.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: quick-start-web
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md

SAM template.yaml 模版剖析

进入到我们sam init 的目录,可以看到有如下结构:

- `src` - Lambda云函数代码目录
- `events` - 可用于调用函数的调用事件。
- `__tests__` - 单元测试目录
- `template.yaml` - 定义应用程序的AWS 资源的模版.

我们先来查看yaml文件。template.yaml文件是SAM模版,SAM模版基于CloudFormation模版,CloudFormation模版用来描述AWS资源以及属性,这将作为构建AWS资源的蓝图。在此基础上,SAM模版引入了几种新的资源和属性类型,它们可以嵌入到模板的资源部分。

为了让CloudFormation识别SAM定义的对象,必须在模版的Transform字段中包含值"AWS::Serverless-2016-10-31"

因此在yaml文件中,我们可以看到以下内容:

AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31

在模版中,必须至少声明一个Resources对象,它包含资源对象的列表。资源对象必须有一个Type属性,它指定了要创建的AWS资源的种类。Type属性的格式如下:

AWS::ProductIdentifier::ResourceType

Resources的组成如下:

Resources:
  Logical ID:
    Type: Resource type
    Properties:
      Set of properties

现在我们查看template.yaml中的Resources部分,其中声明了4个资源,3个是云函数,1个是文档数据库DynamoDB。

数据库的声明比较简单:

Resources:
  SampleTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 2
        WriteCapacityUnits: 2

这里声明了一个具有单个属性主键的DynamoDB表。它通常用于之需要通过主键访问数据的情景。PrimaryKey定义了表的主键名称和类型。ProvisionedThroughput则定义了预置的吞吐量。这里读与写都设置了2个单位。关于单位的详细说明可以看这里open in new window

接着再来看云函数的声明:

  # This is a Lambda function config associated with the source code: put-item.js
  putItemFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/put-item.putItemHandler
      Runtime: nodejs16.x
      Architectures:
        - x86_64
      MemorySize: 128
      Timeout: 100
      Description: A simple example includes a HTTP post method to add one item to a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the SampleTable
        - DynamoDBCrudPolicy:
            TableName: !Ref SampleTable
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          SAMPLE_TABLE: !Ref SampleTable
      Events:
        Api:
          Type: Api
          Properties:
            Path: /
            Method: POST
  # Simple syntax to create a DynamoDB table with a single attribute primary key, more in
  # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable

这里声明了一个逻辑ID为putItemFunction的云函数。看描述可知主要用途是通过Http的post方法,向数据库插入一条数据。

Properties字段下的Handler字段表明了云函数的入口位置,该云函数位于在src/handlers目录下的put-item.js文件,入口函数为该文件的putItemHandler方法。 其他字段表示了代码的执行环境为nodejs16.x;运行为台为x86_64;最大分配128MB的内存,超时时间为100秒。

Policies定义了拥有SampleTable表CRUD的权限,Environment里定义了环境变量,在Nodojs中可以通过

process.env.SAMPLE_TABLE;

来访问。

!Ref 是模版定义好的内部函数,可以用于返回资源的值。这里返回的是表的资源名称。默认情况下,AWS CloudFormation 生成一个唯一的物理 ID 来命名资源。例如,CloudFormation 可能会使用以下物理 ID "stack123123123123-s3bucket-abcdefghijk1" 命名 Amazon S3 存储桶。

最后再看Events字段,该字段用Map数据结构储存了一组字符串(string)到事件源对象(Events source object)的映射。用来定义触发此云函数的方式。

事件源对象用来描述触发函数的事件源的对象,包含Type以及Properties子弹。Type用来表示事件的类型,它可以是' S3 '、' SNS '、' Kinesis '、' MSK '、DynamoDB '、' SQS '、' Api '、' Schedule '、' CloudWatchEvent '、' CloudWatchLogs '、' IoTRule '、' AlexaSkill '事件之一。

在本例中,事件类型都是'Api',表示触发的方式是通过API 网关(API Gateway)。此时Properties字段中Path以及Method字段是必须的。

  • Path 表示访问此云函数的Uri地址。必须以/开头。
  • Method 表示访问此云函数的HTTP method,如POST。

在本例中,我们没有显式地创建AWS::Serverless::Api资源,而是在云函数的事件源中通过Type为Api隐式地创建。此时云函数的输入和输出表示HTTP请求和HTTP响应。例如,使用Javascript API,状态码和body可以通过返回一个带有key为statusCode以及body的对象。

要注意的是,如果显示地定义了AWS::Serverless::Api资源,则Path以及Mathod必须与显式创建时OpenAPI中的操作相符。这里就不展开讨论了。

template中的Globals部分,用于定义云函数和API的通用属性。所有AWS:Serverless:Function和AWS:serverless:Api资源都将继承此处的属性。

Globals:
  Function:
    Tracing: Active
  Api:
    TracingEnabled: True

本例中Globals主要对应我们sam init时,选择的X-Ray跟踪开启操作。

template中的Outputs部分可以定义在控制台中的输出值。这里将会输出api网关的http地址。

至此,template.yaml 模版文件分析完毕。

SAM 部署模版应用

接下来我们部署这个模版应用。

首先执行以下命令:

sam build

若成功将会返回:

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided

然后执行:

sam deploy --guided

根据引导回答问题:

Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: 
AWS Region [ap-northeast-1]: 
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: y
getAllItemsFunction may not have authorization defined, Is this okay? [y/N]: y
getByIdFunction may not have authorization defined, Is this okay? [y/N]: y
putItemFunction may not have authorization defined, Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]: 
SAM configuration environment [default]: 

...

Deploy this changeset? [y/N]: y

成功后,我们将会看到如下输出:

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                
--------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 WebEndpoint                                                                                                                        
Description         API Gateway endpoint URL for Prod stage                                                                                            
Value               https://********.execute-api.ap-northeast-1.amazonaws.com/Prod/                  

这对应template.yaml文件中,Outpus部分的定义。

现在我们登录AWS 控制台,别分进入Lambda、API gateway、DynamoDB,可以看到相关的资源都已被创建。

Lambda Nodejs 操作 DynamoDB

这一节主要分析我们模板创建的云函数代码,了解Lambda云函数如何与DynamoDB数据库交互。

查看package.json的dependencies依赖项,可知主要依赖了aws-sdk。这是AWS提供的开发工具包,可以用它调用各种Amazon Web Services。

sdk-overview-v3
sdk-overview-v3

先来看src/handlers/put-item.js的源码:

// Create clients and set shared const values outside of the handler.

// Create a DocumentClient that represents the query to add an item
const dynamodb = require('aws-sdk/clients/dynamodb');
const docClient = new dynamodb.DocumentClient();

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
 * A simple example includes a HTTP post method to add one item to a DynamoDB table.
 */
exports.putItemHandler = async (event) => {
  if (event.httpMethod !== 'POST') {
    throw new Error(`postMethod only accepts POST method, you tried: ${event.httpMethod} method.`);
  }
  // All log statements are written to CloudWatch
  console.info('received:', event);

  // Get id and name from the body of the request
  const body = JSON.parse(event.body);
  const { id } = body;
  const { name } = body;

  // Creates a new item, or replaces an old item with a new item
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property
  let response = {};

  try {
    const params = {
      TableName: tableName,
      Item: { id, name },
    };

    const result = await docClient.put(params).promise();

    response = {
      statusCode: 200,
      body: JSON.stringify(body),
    };
  } catch (ResourceNotFoundException) {
    response = {
      statusCode: 404,
      body: 'Unable to call DynamoDB. Table resource not found.',
    };
  }

  // All log statements are written to CloudWatch
  console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
  return response;
};

上述代码通过aws-sdk/clients/dynamodb工具类,向数据库插入了一条{id, name}的数据。留意put方法的参数:

  • TableName 表名
  • Item 可序列化的 JavaScript 对象。

在上一节中,我们已经部署了应用。若我们在命令行中输入(把********换成你的地址):

curl -XPOST "https://********.execute-api.ap-northeast-1.amazonaws.com/Prod/" -d '{"id": "1","name": "test"}

此时在AWS dynamodb 中可以查到如下数据:

idname
1test

读取数据库部分与上述代码类似,核心部分如下:

// Get id from pathParameters from APIGateway because of `/{id}` at template.yaml
  const { id } = event.pathParameters;

  // Get the item from the table
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property
  let response = {};

  try {
    const params = {
      TableName: tableName,
      Key: { id },
    };
    const data = await docClient.get(params).promise();
    const item = data.Item;

    response = {
      statusCode: 200,
      body: JSON.stringify(item),
    };
  } catch (ResourceNotFoundException) {
    response = {
      statusCode: 404,
      body: 'Unable to call DynamoDB. Table resource not found.',
    };
  }

更对关于数据库的操作,可以查看适用于 JavaScript 的 AWS 开发工具包

参考

[1]: AWS Serverless Application Model (SAM)open in new window

[2]: resources-section-structureopen in new window

[3]: intrinsic-function-reference-refopen in new window

[4]: 适用于 JavaScript 的 AWS 开发工具包open in new window

上次编辑于:
Loading...