无服务器 - 带有 DynamoDB 的 REST API


到目前为止,我们已经学习了与无服务器 lambda 部署相关的几个概念。现在是时候看一些例子了。在本章中,我们将看一下 Serverless 官方提供的一个示例。顾名思义,我们将创建一个 REST API。正如您所猜测的,我们所有的 lambda 函数都将由 API 网关触发。我们的 lambda 函数将与 dynamoDB 表交互,该表本质上是一个待办事项列表,用户将能够使用以下端点执行多项操作,例如创建新项目、获取现有项目、删除项目等。在部署后暴露。如果您不熟悉 REST API,那么您可以在此处阅读有关它们的更多信息。

代码演练

代码可以在 GitHub 上找到 - https://github.com/serverless/examples/tree/master/aws-python-rest-api-with-dynamodb

我们将查看项目结构,讨论一些迄今为止尚未见过的新概念,然后执行 serverless.yml 文件的演练。所有函数处理程序的演练将是多余的。因此,我们将仅介绍一个函数处理程序。您可以将理解其他函数作为练习。

项目结构

现在,如果您查看项目结构,就会发现 lambda 函数处理程序都位于 todos 文件夹中单独的 .py 文件中。serverless.yml 文件指定每个函数处理程序路径中的 todos 文件夹。没有外部依赖项,因此没有requirements.txt 文件。

新概念

现在,有几个术语您可能是第一次看到。让我们快速浏览一下这些 -

除此之外,我们在演练 serverless.yml 和处理程序函数时还会遇到一些概念。我们将在那里讨论它们。

serverless.yml 演练

serverless.yml 文件以服务的定义开始。

service: serverless-rest-api-with-dynamodb

接下来是通过以下行声明框架版本范围 -

frameworkVersion: ">=1.1.0 <=2.1.1"

这就像一张支票。如果您的无服务器版本不在此范围内,它将抛出错误。当您共享代码并希望使用此 serverless.yml 文件的每个人都使用相同的无服务器版本范围以避免问题时,这会有所帮助。

接下来,在提供程序中,我们看到两个迄今为止尚未遇到的额外字段 -环境iamRoleStatements

provider:
   name: aws
   runtime: python3.8
   environment:
      DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
   iamRoleStatements:
      - Effect: Allow
         Action:
         - dynamodb:Query
         - dynamodb:Scan
         - dynamodb:GetItem
         - dynamodb:PutItem
         - dynamodb:UpdateItem
         - dynamodb:DeleteItem
         Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:
         *:table/${self:provider.environment.DYNAMODB_TABLE}"

正如您可能已经猜到的,环境用于定义环境变量。此 serverless.yml 文件中定义的所有函数都可以获取这些环境变量。我们将在下面的函数处理程序演练中看到一个示例。在这里,我们将 dynamoDB 表名称定义为环境变量。

$符号表示变量。self关键字指的是 serverless.yml 文件本身,而opt指的是我们可以在sls deploy期间提供选项。因此,表名称将是服务名称,后跟连字符,后跟文件找到的第一个阶段参数:无服务器部署期间选项中可用的一个,或者提供者阶段(默认情况下为 dev)。因此,在本例,如果您在无服务器部署期间不提供任何选项,则 dynamoDB 表名称将为 serverless-rest-api-with-dynamodb-dev。您可以在此处阅读有关无服务器变量的更多信息。

iamRoleStatements定义提供给函数的权限。在这种情况下,我们允许函数对 dynamoDB 表执行以下操作 - QueryScanGetItemPutItemUpdateItemDeleteItem。资源名称指定允许执行这些操作的确切表。如果您输入“*”代替资源名称,则将允许对所有表执行这些操作。但是,在这里,我们希望只允许在一张表上进行这些操作,因此,该表的 arn(Amazon 资源名称)在资源名称中提供,使用标准 arn 格式。这里再次使用选项区域(在无服务器部署期间指定)或提供程序中提到的区域(默认为 us-east-1)中的第一个区域。

在函数部分,函数是按照标准格式定义的。注意,get、update、delete 都有相同的路径,以 id 作为路径参数。但是,每个人的方法都不同。

functions:
   create:
      handler: todos/create.create
      events:
         - http:
            path: todos
            method: post
            cors: true
   list:
      handler: todos/list.list
      events:
         - http:
            path: todos
            method: get
            cors: true
   get:
      handler: todos/get.get
      events:
         - http:
            path: todos/{id}
            method: get
            cors: true

   update:
      handler: todos/update.update
      events:
         - http:
            path: todos/{id}
            method: put
            cors: true
   delete:
      handler: todos/delete.delete
      events:
         - http:
            path: todos/{id}
            method: delete
            cors: true

后来,我们遇到了另一个我们以前从未见过的块,即资源块。此块主要帮助您指定在 CloudFormation 模板中创建所需的资源,以使功能正常工作。在这种情况下,我们需要创建一个 dynamoDB 表才能使函数发挥作用。到目前为止,我们已经指定了表的名称,甚至引用了它的 ARN。但我们还没有创建表。在资源块中指定表的特征将为我们创建该表。

resources:
   Resources:
      TodosDynamoDbTable:
         Type: 'AWS::DynamoDB::Table'
         DeletionPolicy: Retain
         Properties:
         AttributeDefinitions:
            -
               AttributeName: id
               AttributeType: S
         KeySchema:
            -
               AttributeName: id
               KeyType: HASH
         ProvisionedThroughput:
            ReadCapacityUnits: 1
            WriteCapacityUnits: 1
            TableName: ${self:provider.environment.DYNAMODB_TABLE}

这里定义了很多配置,其中大多数特定于 dynamoDB。简而言之,我们要求无服务器创建一个“TodosDynamoDbTable”,或输入“DynamoDB Table”,其中 TableName(在底部右侧提到)等于提供程序中环境变量中定义的值。我们将其删除策略设置为“保留”,这意味着如果堆栈被删除,资源将被保留。请参阅此处。我们说该表将有一个名为id的属性,其类型为 String。我们还指定 id 属性将是 HASH 键或分区键。您可以在此处阅读有关 dynamoDB 表中的 KeySchema 的更多信息。最后,我们指定表的读取容量和写入容量。

就是这样!我们的 serverless.yml 文件现已准备就绪。现在,由于所有函数处理程序都或多或少相似,因此我们将仅介绍一个处理程序,即 create 函数的处理程序。

创建函数处理程序的演练

我们有几个导入声明

import json
import logging
import os
import time
import uuid

接下来,我们导入 boto3,如上所述,它是适用于 python 的 AWS 开发工具包。我们需要 boto3 从 lambda 函数内与 dynamoDB 进行交互。

import boto3
dynamodb = boto3.resource('dynamodb')

接下来,在实际的函数处理程序中,我们首先检查“事件”有效负载的内容(创建 API 使用 post 方法)。如果其正文不包含“文本”键,则我们尚未收到要添加到待办事项列表中的有效项目。因此,我们提出一个例外。

def create(event, context):
   data = json.loads(event['body'])
   if 'text' not in data:
      logging.error("Validation Failed")
      raise Exception("Couldn't create the todo item.")

考虑到我们按预期获得了“text”键,我们准备将其添加到 dynamoDB 表中。我们获取当前时间戳,并连接到 dynamoDB 表。注意如何获取 serverless.yml 中定义的环境变量(使用 os.environ)

timestamp = str(time.time())
table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])

接下来,我们创建要添加到表中的项目,方法是使用 uuid 包生成随机 uuid,使用接收到的数据作为文本,将createdAt 和updatedAt 设置为当前时间戳,并将字段“checked”设置为False。除了文本之外,“checked”是您可以使用更新操作更新的另一个字段。

item = {
   'id': str(uuid.uuid1()),
   'text': data['text'],
   'checked': False,
   'createdAt': timestamp,
   'updatedAt': timestamp,
}

最后,我们将项目添加到 dynamoDB 表中,并将创建的项目返回给用户。

# write the todo to the database
table.put_item(Item=item)

# create a response
response = {
   "statusCode": 200,
   "body": json.dumps(item)
}
return response

通过本演练,我认为其他函数处理程序将是不言自明的。在某些函数中,您可能会看到这样的语句 - "body" - json.dumps(result['Item'], cls=decimalencoder.DecimalEncoder)。这是针对 json.dumps 中的错误的解决方法。默认情况下,json.dumps 无法处理十进制数字,因此,创建了decimalencoder.py文件来包含处理此问题的 DecimalEncoder 类。

祝贺您了解使用无服务器创建的第一个综合项目。该项目的创建者还在自述文件中分享了他的部署端点以及测试这些功能的方法。看一看。请继续下一章查看另一个示例。