
无服务器 - 使用 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 文件。
新概念
现在,有一些术语您可能是第一次看到。让我们快速浏览一下:
DynamoDB - 这是 AWS 提供的 NoSQL(不仅仅是 SQL)数据库。虽然不是完全准确,但广义地说,NoSQL 之于 SQL,就像 Word 之于 Excel。您可以点击这里了解更多关于 NoSQL 的信息。有四种类型的 NoSQL 数据库:文档数据库、键值数据库、列式存储和图数据库。DynamoDB 是一个键值数据库,这意味着您可以将键值对不断插入到数据库中。这类似于 Redis 缓存。您可以通过引用其键来检索值。
Boto3 - 这是 Python 的 AWS SDK。如果您需要在 Lambda 函数中配置、管理、调用或创建任何 AWS 服务(EC2、DynamoDB、S3 等),则需要 Boto3 SDK。您可以点击这里了解更多关于 Boto3 的信息。
除此之外,在 serverless.yml 和处理程序函数的演练过程中,我们将遇到一些概念。我们将在那里讨论它们。
serverless.yml 演练
serverless.yml 文件以服务的定义开始。
service: serverless-rest-api-with-dynamodb
接下来是通过以下行声明框架版本范围:
frameworkVersion: ">=1.1.0 <=2.1.1"
这就像一个检查。如果您的无服务器版本不在此范围内,它将抛出错误。当您共享代码并希望每个人都使用此 serverless.yml 文件使用相同的无服务器版本范围以避免问题时,这很有帮助。
接下来,在 provider 中,我们看到两个我们之前没有遇到的额外字段:environment 和 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}"
Environment,正如您可能猜到的那样,用于定义环境变量。在此 serverless.yml 文件中定义的所有函数都可以获取这些环境变量。我们将在下面的函数处理程序演练中看到一个示例。在这里,我们将 DynamoDB 表名称定义为环境变量。
$ 符号表示变量。self 关键字指的是 serverless.yml 文件本身,而 opt 指的是我们在 sls deploy 期间可以提供的选项。因此,表名称将是服务名称后跟连字符,然后是文件找到的第一个阶段参数:sls deploy 期间可用的一个选项,或者 provider 阶段(默认为 dev)。因此,在这种情况下,如果您在 serverless deploy 期间不提供任何选项,DynamoDB 表名称将为 serverless-rest-api-with-dynamodb-dev。您可以点击这里了解更多关于无服务器变量的信息。
iamRoleStatements 定义授予函数的权限。在这种情况下,我们允许函数对 DynamoDB 表执行以下操作:Query、Scan、GetItem、PutItem、UpdateItem 和 DeleteItem。资源名称指定允许执行这些操作的确切表。如果您在资源名称的位置输入了"*",则您将允许对所有表执行这些操作。但是,这里我们只想允许对一个表执行这些操作,因此,此表的 ARN(Amazon 资源名称)使用标准 ARN 格式提供在资源名称中。同样,这里使用选项区域(在 serverless deploy 期间指定)或 provider 中提到的区域(默认为 us-east-1)中的第一个。
在 functions 部分,函数按照标准格式定义。请注意,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
稍后,我们遇到了另一个我们之前没有见过的块,即resources 块。此块基本上可以帮助您指定您需要为函数工作而在 CloudFormation 模板中创建的资源。在这种情况下,我们需要创建一个 DynamoDB 表才能使函数工作。到目前为止,我们已经指定了表的名称,甚至引用了它的 ARN。但是我们还没有创建表。在resources 块中指定表的特性将为我们创建该表。
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(最底部提到)等于在 provider 的环境变量中定义的名称。我们将它的删除策略设置为 'Retain',这意味着如果堆栈被删除,则保留资源。参见这里。我们说表将有一个名为id的属性,其类型为 String。我们还指定 id 属性将是 HASH 键或分区键。您可以点击这里了解更多关于 DynamoDB 表中 KeySchemas 的信息。最后,我们指定表的读取容量和写入容量。
就是这样!我们的 serverless.yml 文件现在已准备好。现在,由于所有函数处理程序都大致相似,我们将只演练一个处理程序,即 create 函数的处理程序。
create 函数处理程序演练
我们从几个导入语句开始
import json import logging import os import time import uuid
接下来,我们导入 boto3,如上所述,它是 Python 的 AWS SDK。我们需要 boto3 来在 Lambda 函数中与 DynamoDB 交互。
import boto3 dynamodb = boto3.resource('dynamodb')
接下来,在实际的函数处理程序中,我们首先检查 'events' 负载的内容(create API 使用 post 方法)。如果它的主体不包含 'text' 键,则我们没有收到要添加到待办事项列表中的有效项目。因此,我们引发异常。
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。除了文本之外,您还可以使用 update 操作更新 '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 类。
恭喜您理解了使用无服务器创建的第一个综合项目。项目的创建者还在README文件中分享了他的部署端点以及测试这些函数的方法。请查看。进入下一章以查看另一个示例。