教程:Aurora Serverless - Amazon AppSync
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

教程:Aurora Serverless

Amazon AppSync 提供了一个数据源,用于对已启用数据 API 的 Amazon Aurora Serverless 集群执行 SQL 命令。您可以使用 AppSync 解析器通过 GraphQL 查询、变更和订阅对数据 API 执行 SQL 语句。

创建集群

在将 RDS 数据源添加到 AppSync 之前,您必须先在 Aurora Serverless 集群上启用数据 API 并使用 Amazon Secrets Manager 配置密钥。您可以先使用 Amazon CLI 创建一个 Aurora Serverless 集群:

aws rds create-db-cluster --db-cluster-identifier http-endpoint-test --master-username USERNAME \ --master-user-password COMPLEX_PASSWORD --engine aurora --engine-mode serverless \ --region us-east-1

这将为集群返回一个 ARN。

使用上一步中的 USERNAME 和 COMPLEX_PASSWORD 通过 Amazon Secrets Manager 控制台创建一个密钥,也可以通过 CLI 使用如下输入文件创建密钥:

{ "username": "USERNAME", "password": "COMPLEX_PASSWORD" }

将其作为参数传递给 Amazon CLI:

aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1

这将为密钥返回 ARN。

记下稍后在创建数据源时在 AppSync 控制台中使用的 Aurora Serverless 集群和密钥的 ARN。

启用数据 API

您可以通过按照 RDS 文档中的说明操作来在您的集群上启用数据 API。在将数据 API 作为 AppSync 数据源添加之前,必须先启用它。

创建数据库和表

在启用数据 API 后,您可以确保它在 Amazon CLI 中使用 aws rds-data execute-statement 命令。这将确保在将 Aurora Serverless 集群添加到 AppSync API 之前已进行正确配置。首先,使用 --sql 参数创建一个名为 TESTDB 的数据库,如下所示:

aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \ --schema "mysql" --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \ --region us-east-1 --sql "create DATABASE TESTDB"

如果运行无误,请使用创建表 命令添加一个表:

aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \ --schema "mysql" --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \ --region us-east-1 \ --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"

如果一切运行正常,您可以继续在 AppSync API 中将集群添加为数据源。

GraphQL 架构

现在,您的 Aurora Serverless 数据 API 已通过表启动并运行,我们将创建一个 GraphQL 架构并附加用于执行变更和订阅的解析器。在 Amazon AppSync 控制台中创建一个新的 API,并导航到架构页面,然后输入以下内容:

type Mutation { createPet(input: CreatePetInput!): Pet updatePet(input: UpdatePetInput!): Pet deletePet(input: DeletePetInput!): Pet } input CreatePetInput { type: PetType price: Float! } input UpdatePetInput { id: ID! type: PetType price: Float! } input DeletePetInput { id: ID! } type Pet { id: ID! type: PetType price: Float } enum PetType { dog cat fish bird gecko } type Query { getPet(id: ID!): Pet listPets: [Pet] listPetsByPriceRange(min: Float, max: Float): [Pet] } schema { query: Query mutation: Mutation }

保存您的架构并导航到数据源页面,然后创建新的数据源。为数据源类型选择关系数据库,并提供一个友好名称。使用您在上一步中创建的数据库名称以及用来创建它的集群 ARN。对于角色,您可以让 AppSync 创建新角色,也可以使用与以下内容类似的策略创建角色:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "rds-data:DeleteItems", "rds-data:ExecuteSql", "rds-data:ExecuteStatement", "rds-data:GetItems", "rds-data:InsertItems", "rds-data:UpdateItems" ], "Resource": [ "arn:aws:rds:us-east-1:123456789012:cluster:mydbcluster", "arn:aws:rds:us-east-1:123456789012:cluster:mydbcluster:*" ] }, { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": [ "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret", "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret:*" ] } ] }

请注意,此策略中有两个需要获得角色访问权的语句。第一个资源是您的 Aurora Serverless 集群,第二个资源是您的 Amazon Secrets Manager ARN。在单击创建之前,您需要在 AppSync 数据源配置中提供这两个 ARN。

配置解析器

现在,我们有一个有效的 GraphQL 架构和一个 RDS 数据源,我们可以将解析器附加到架构上的 GraphQL 字段。我们的 API 将提供以下功能:

  1. 通过 Mutation.createPet 字段创建宠物

  2. 通过 Mutation.updatePet 字段更新宠物

  3. 通过 Mutation.deletePet 字段删除宠物

  4. 通过 Query.getPet 字段获取单个宠物

  5. 通过 Query.listPets 字段列出所有宠物

  6. 通过 Query.listPetsByPriceRange 字段列出价格范围内的宠物

Mutation.createPet

从 Amazon AppSync 控制台的架构编辑器中,在右侧为 createPet(input: CreatePetInput!): Pet 选择附加解析器。选择您的 RDS 数据源。在 request mapping template (请求映射模板) 部分中,添加以下模板:

#set($id=$utils.autoId()) { "version": "2018-05-29", "statements": [ "insert into Pets VALUES (:ID, :TYPE, :PRICE)", "select * from Pets WHERE id = :ID" ], "variableMap": { ":ID": "$ctx.args.input.id", ":TYPE": $util.toJson($ctx.args.input.type), ":PRICE": $util.toJson($ctx.args.input.price) } }

SQL 语句将根据语句数组中的顺序依次执行。结果将以相同的顺序返回。由于这是一个变更,我们在 insert 后面运行 select 语句以检索提交的值,以便填充 GraphQL 响应映射模板。

response mapping template (响应映射模板) 部分中,添加以下模板:

$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])

由于语句有两个 SQL 查询,我们需要指定从具有 $utils.rds.toJsonString($ctx.result))[1][0]) 的数据库返回的矩阵中的第二个结果。

Mutation.updatePet

从 Amazon AppSync 控制台的架构编辑器中,在右侧为 updatePet(input: UpdatePetInput!): Pet 选择附加解析器。选择您的 RDS 数据源。在 request mapping template (请求映射模板) 部分中,添加以下模板:

{ "version": "2018-05-29", "statements": [ $util.toJson("update Pets set type=:TYPE, price=:PRICE WHERE id=:ID"), $util.toJson("select * from Pets WHERE id = :ID") ], "variableMap": { ":ID": "$ctx.args.input.id", ":TYPE": $util.toJson($ctx.args.input.type), ":PRICE": $util.toJson($ctx.args.input.price) } }

response mapping template (响应映射模板) 部分中,添加以下模板:

$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])

Mutation.deletePet

从 Amazon AppSync 控制台的架构编辑器中,在右侧为 deletePet(input: DeletePetInput!): Pet 选择附加解析器。选择您的 RDS 数据源。在 request mapping template (请求映射模板) 部分中,添加以下模板:

{ "version": "2018-05-29", "statements": [ $util.toJson("select * from Pets WHERE id=:ID"), $util.toJson("delete from Pets WHERE id=:ID") ], "variableMap": { ":ID": "$ctx.args.input.id" } }

response mapping template (响应映射模板) 部分中,添加以下模板:

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])

Query.getPet

现在,已为您的架构创建变更,我们将连接三个查询以展示如何获取各个项目和列表以及应用 SQL 筛选。从 Amazon AppSync 控制台的架构编辑器中,在右侧为 getPet(id: ID!): Pet 选择附加解析器。选择您的 RDS 数据源。在 request mapping template (请求映射模板) 部分中,添加以下模板:

{ "version": "2018-05-29", "statements": [ $util.toJson("select * from Pets WHERE id=:ID") ], "variableMap": { ":ID": "$ctx.args.id" } }

response mapping template (响应映射模板) 部分中,添加以下模板:

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])

Query.listPets

从 Amazon AppSync 控制台的架构编辑器中,在右侧为 getPet(id: ID!): Pet 选择附加解析器。选择您的 RDS 数据源。在 request mapping template (请求映射模板) 部分中,添加以下模板:

{ "version": "2018-05-29", "statements": [ "select * from Pets" ] }

response mapping template (响应映射模板) 部分中,添加以下模板:

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])

Query.listPetsByPriceRange

从 Amazon AppSync 控制台的架构编辑器中,在右侧为 getPet(id: ID!): Pet 选择附加解析器。选择您的 RDS 数据源。在 request mapping template (请求映射模板) 部分中,添加以下模板:

{ "version": "2018-05-29", "statements": [ "select * from Pets where price > :MIN and price < :MAX" ], "variableMap": { ":MAX": $util.toJson($ctx.args.max), ":MIN": $util.toJson($ctx.args.min) } }

response mapping template (响应映射模板) 部分中,添加以下模板:

$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])

运行变更

现在,您已使用 SQL 语句配置所有解析器并将 GraphQL API 连接到 Serverless Aurora 数据 API,可以开始执行变更和查询。在 Amazon AppSync 控制台中,选择查询选项卡,并输入以下内容以创建一个宠物:

mutation add { createPet(input : { type:fish, price:10.0 }){ id type price } }

该响应包含 id类型价格,如下所示:

{ "data": { "createPet": { "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a", "type": "fish", "price": "10.0" } } }

您可以通过运行 updatePet 变更来修改此项目:

mutation update { updatePet(input : { id: ID_PLACEHOLDER, type:bird, price:50.0 }){ id type price } }

请注意,我们使用了以前从 createPet 操作返回的 id。当解析器利用 $util.autoId() 时,这将是您的记录的唯一值。您可以通过类似的方式删除记录:

mutation delete { deletePet(input : {id:ID_PLACEHOLDER}){ id type price } }

使用包含不同的价格 值的第一个变更创建几条记录,然后运行几个查询。

运行查询

同样,在控制台的查询选项卡中,使用以下语句列出您创建的所有记录:

query allpets { listPets { id type price } }

这很好,但让我们在以下 GraphQL 查询中使用在 Query.listPetsByPriceRange 映射模板中包含 where price > :MIN and price < :MAX 的 SQL WHERE 谓词:

query petsByPriceRange { listPetsByPriceRange(min:1, max:11) { id type price } }

您应仅看到包含高于 1 美元或低于 10 美元的价格 的记录。最后,您可以执行查询以检索各个记录,如下所示:

query onePet { getPet(id:ID_PLACEHOLDER){ id type price } }

输入净化

我们建议开发人员使用 variableMap 以防范 SQL 注入攻击。如果不使用变量映射,则由开发人员负责清理其 GraphQL 操作的参数。执行此操作的一种方法是在对数据 API 执行 SQL 语句之前,在请求映射模板中提供特定于输入的验证步骤。让我们看看如何修改 listPetsByPriceRange 示例的请求映射模板。您可以执行以下操作,而不是仅依赖于用户输入:

#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice)) #set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice)) #if (!$validMaxPrice || !$validMinPrice) $util.error("Provided price input is not valid.") #end { "version": "2018-05-29", "statements": [ "select * from Pets where price > :MIN and price < :MAX" ], "variableMap": { ":MAX": $util.toJson($ctx.args.maxPrice), ":MIN": $util.toJson($ctx.args.minPrice) } }

在对数据 API 执行解析器时防止恶意输入的另一种方法是,将预编译语句与存储过程和参数化输入一起使用。例如,在 listPets 的解析器中,定义以下将 select 作为预编译语句执行的过程:

CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END

可以使用以下 execute sql 命令在 Aurora Serverless 实例中创建此内容:

aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \ --schema "mysql" --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx" \ --region us-east-1 --database "DB_NAME" \ --sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"

由于我们现在只是调用了存储过程,因此简化了为 listPets 生成的解析器代码。至少,任何字符串输入都应该有单引号进行转义

#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type)) #if (!$validType) $util.error("Input for 'type' is not valid.", "ValidationError") #end { "version": "2018-05-29", "statements": [ "CALL listPets(:type)" ] "variableMap": { ":type": $util.toJson($ctx.args.type.replace("'", "''")) } }

转义字符串

单引号表示 SQL 语句中字符串文字的开始和结束,例如,'some string value'。要允许在字符串中使用具有一个或多个单引号字符 ( ') 的字符串值,必须用两个单引号 ('') 替换每个字符串值。例如,如果输入字符串是 Nadia's dog,您可以针对 SQL 语句将其转义,例如

update Pets set type='Nadia''s dog' WHERE id='1'