Serverless 简明教程

Serverless - REST API with DynamoDB

到目前为止,我们已经学习了与无服务器 lambda 部署相关的多个概念。现在是时候查看一些示例了。在本章节中,我们将查看 Serverless 正式提供的一个示例。正如其名,我们将创建一个 REST API。正如你所猜测的,我们所有 lambda 函数将由 API Gateway 触发。我们的 lambda 函数将与 dynamoDB 表(本质上这是一个待办事项列表)交互,并且用户将能够执行一系列操作,例如创建新项目、获取现有项目、删除项目等,这些操作将使用部署后公开的端点。如果你不熟悉 REST API,那么可以阅读更多内容 here

So far, we have learned several concepts related to serverless lambda deployments. Now it is time to look at some examples. In this chapter, we will look at one of the examples officially provided by Serverless. We will be creating, as the name suggests, a REST API. All our lambda functions, as you would have guessed, will be triggered by an API Gateway. Our lambda functions will interface with a dynamoDB table, which is a to-do list essentially, and the user will be able to perform several operations, like creating a new item, fetching existing items, deleting items, etc. using the endpoints that will be exposed post the deployment.If you are not familiar with REST APIs, then you can read up more about them here.

Code Walkthrough

我们将查看项目结构,讨论一些我们迄今为止尚未看到的新概念,然后完成 serverless.yml 文件演练。所有函数处理器的演练是冗余的。因此,我们将仅完成一个函数处理器演练。你可以将理解其他函数作为练习来执行。

We will have a look at the project structure, discuss some new concepts that we haven’t seen so far, and then perform the walkthrough of the serverless.yml file. The walkthrough of all the function handlers will be redundant. Therefore, we will walk through just one function handler. You can take up understanding the other functions as an exercise.

Project Structure

现在,如果你查看项目结构,lambda 函数处理器都在 todos 文件夹中的不同 .py 文件中。serverless.yml 文件在每个函数处理器的路径中指定了 todos 文件夹。没有外部依赖,因此没有 requirements.txt 文件。

Now, if you look at the project structure, the lambda function handlers are all within separate .py files in the todos folder. The serverless.yml file specifies the todos folder in the path of each function handler. There are no external dependencies, and therefore, no requirements.txt file.

New Concepts

现在,有一些术语可能是你第一次见到。让我们快速浏览一下−

Now, there are a couple of terms that you may be seeing for the first time. Let’s scan these quickly −

  1. dynamoDB − This is a NoSQL (Not only SQL) database provided by AWS. While not exactly accurate, broadly speaking, NoSQL is to SQL what Word is to Excel. You can read more about NoSQL here. There are 4 types of NoSQL databases − Document databases, key-value databases, wide-column stores, and graph databases. dynamoDB is a key-value database, meaning that you can keep inserting key-value pairs into the database. This is similar to redis cache. You can retrieve the value by referencing its key.

  2. boto3 − This is the AWS SDK for Python. If you need to configure, manage, call, or create any service of AWS (EC2, dynamoDB, S3, etc.) within the lambda function, you need the boto3 SDK.You can read up more about boto3 here.

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

Apart from these, there are some concepts that we will encounter during the walkthrough of the serverless.yml and the handler function. We will discuss them there.

serverless.yml Walkthrough

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

The serverless.yml file begins with the definition of the service.

service: serverless-rest-api-with-dynamodb

然后是通过以下行声明框架版本范围−

That is followed by the declaration of the framework version range through the following line −

frameworkVersion: ">=1.1.0 <=2.1.1"

这类似于检查。如果你的无服务器版本不在该范围内,它将抛出错误。当你共享代码并希望使用此 serverless.yml 文件的所有人使用相同的无服务器版本范围以避免问题时,这将有所帮助。

This acts like a check. If your serverless version doesn’t lie in this range, it will throw up an error. This helps when you are sharing code and would want everyone using this serverless.yml file to use the same serverless version range to avoid problems.

接下来,在提供程序中,我们看到了以前没有遇到的两个额外字段− environmentiamRoleStatements

Next, within the provider, we see two extra fields that we haven’t encountered so far − environment and 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 表名定义为一个环境变量。

Environment, as you would have guessed, is used to define environment variables. All the functions defined within this serverless.yml file can fetch these environment variables. We will see an example in the function handler walkthrough below. Over here, we are defining the dynamoDB table name as an environment variable.

$ 符号表示一个变量。 self 关键字表示 serverless.yml 文件本身,而 opt 表示我们可以在 sls deploy 期间提供的选项。因此,表名将是服务名,后跟连字符,然后是文件找到的第一个阶段参数:在 serverless 部署期间从选项中获得一个,或提供程序阶段,默认是 dev 。因此,在这种情况下,如果你在 serverless 部署期间未提供任何选项,dynamoDB 表名将是 serverless-rest-api-with-dynamodb-dev。你可以阅读有关无服务器变量 here. 的更多内容

The $ sign signifies a variable. The self keyword refers to the serverless.yml file itself, while opt refers to an option that we can provide during sls deploy. Thus, the table name will be the service name followed by a hyphen followed by the first stage parameter that the file finds: either one available from options during serverless deploy, or the provider stage, which is dev by default.Thus, in this case, if you don’t provide any option during serverless deploy, the dynamoDB table name will be serverless-rest-api-with-dynamodb-dev. You can read more about serverless variables here.

iamRoleStatements 定义提供给函数的权限。在这种情况下,我们允许 allowing 函数对 dynamoDB 表执行以下操作− QueryScanGetItemPutItemUpdateItemDeleteItem 。资源名称指定允许这些操作的确切表。如果你在资源名称处输入 " "*,则可以在所有表上进行这些操作。但是,此处,我们只想在其中一张表上允许这些操作,因此此表的 arn(Amazon 资源名称)使用标准 arn 格式提供在资源名称中。此处再次使用了选项区域(在 serverless 部署期间指定)或提供程序中提到的区域(默认是 us-east-1)中的第一个。

iamRoleStatements define permissions provided to the functions. In this case, we are allowing the functions to perform the following operations on the dynamoDB table − Query, Scan, GetItem, PutItem, UpdateItem, and DeleteItem. The Resource name specifies the exact table on which these operations are allowed.If you had entered ""* in place of the resource name, you would have allowed these operations on all the tables. However, here, we want to allow these operations on just one table, and therefore, the arn (Amazon Resource Name) of this table is provided in the Resource name, using the standard arn format. Here again, the first one of either the option region (specified during serverless deploy) or the region mentioned in provider (us-east-1 by default)is used.

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

In the functions section, the functions are defined as per the standard format. Notice that get, update, delete all have the same path, with id as the path parameter. However, the method is different for each.

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 块中指定表的特征将为我们创建该表。

Later on, we come across another block that we haven’t seen before, the resources block. This block basically helps you specify the resources that you will need to create, in a CloudFormation template, for the functions to work. In this case, we need to create a dynamoDB table for the functions to work. So far, we have specified the name of the table, and even referenced its ARN. But we haven’t created the table. Specifying the characteristics of the table in the resources block will create that table for us.

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 表”的表,该表的 TableName(在底部右方提及)等于 provider 中环境变量中定义的 TableName。我们将它的删除策略设置为“保留”,这意味着如果堆栈被删除,则保留该资源。请参见 here 。我们说该表将具有一个名为 id 的属性,并且它的类型将是字符串。我们还指定 id 属性将是一个 HASH 密钥或分区密钥。您可以在 dynamoDB 表中详细了解 KeySchemas here 。最后,我们指定表的读取容量和写入容量。

There are a lot of configurations being defined here, most of them specific to dynamoDB. Briefly, we are asking serverless to create a 'TodosDynamoDbTable', or type 'DynamoDB Table', with TableName (mentioned right at the bottom) equal to the one defined in environment variables in provider. We are setting its deletion policy to 'Retain', which means that if the stack is deleted, the resource is retained. See here. We are saying that the table will have an attribute named id, and its type will be String. We are also specifying that the id attribute will be a HASH key or a partition key. You can read up more about KeySchemas in dynamoDB tables here. Finally, we are specifying the read capacity and write capacity of the table.

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

That’s it! Our serverless.yml file is now ready. Now, since all the function handlers are more or less similar, we will walk through just one handler, that of the create function.

Walkthrough of the create function handler

我们从几个导入语句开始

We being with a couple of import statements

import json
import logging
import os
import time
import uuid

接下来,我们导入 boto3,如上所述,它是 python 的 AWS SDK。我们需要 boto3 通过 lambda 函数与 dynamoDB 交互。

Next, we import boto3, which, as described above, is the AWS SDK for python. We need boto3 to interface with dynamoDB from within the lambda function.

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

接下来,在实际的函数处理程序中,我们首先检查“events”payload 的内容(create API 使用 post 方法)。如果它的正文不包含“text”键,我们就没有收到要添加到 todo 列表的有效项目。因此,我们引发异常。

Next, in the actual function handler, we first check the contents of the 'events' payload (create API uses the post method). If its body doesn’t contain a 'text' key, we haven’t received a valid item to be added to the todo list. Therefore, we raise an exception.

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)

Considering that we got the 'text' key as expected, we make preparations for adding it to the dynamoDB table. We fetch the current timestamp, and connect to the dynamoDB table. Notice how the environment variable defined in serverless.yml is fetched (using os.environ)

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

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

Next, we create the item to be added to the table, by generating a random uuid using the uuid package, using the received data as text, setting createdAt and updatedAt to the current timestamp,and setting the field 'checked' to False. 'checked' is another field which you can update, apart from text, using the update operation.

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

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

Finally, we add the item to the dynamoDB table and return the created item to the user.

# 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 中 bug 的解决方法。json.dumps 默认情况下无法处理小数,因此,已创建 decimalencoder.py 文件来包含处理此内容的 DecimalEncoder 类。

With this walkthrough, I think the other function handlers will be self-explanatory. In some functions, you may see this statement − "body" − json.dumps(result['Item'], cls=decimalencoder.DecimalEncoder). This is a workaround used for a bug in json.dumps. json.dumps can’t handle decimal numbers by default and therefore, the decimalencoder.py file has been created to contain the DecimalEncoder class which handles this.

恭喜您理解您使用无服务器创建的第一个综合项目。该项目创建者还在 README 文件中共享了他的部署端点以及测试这些函数的方法。看一看。前往下一章以查看另一个示例。

Congratulations on understanding your first comprehensive project created using serverless. The creator of the project has also shared the endpoints of his deployment and the ways to test these functions in the README file. Have a look. Head on to the next chapter to see another example.