将 Aurora Serverless v2 与 AWS AppSync - AWS AppSync GraphQL

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

将 Aurora Serverless v2 与 AWS AppSync

使用将您的 GraphQL API 连接到 Aurora 无服务器数据库。 AWS AppSync这种集成允许您通过 GraphQL 查询、突变和订阅来执行 SQL 语句,从而为您提供与关系数据交互的灵活方式。

注意

本教程使用 US-EAST-1 区域。

优势
  • GraphQL 和关系数据库之间的无缝集成

  • 能够通过 GraphQL 接口执行 SQL 操作

  • 使用 Aurora 无服务器 v2 实现无服务器可扩展性

  • 通过 Secr AWS ets Manager 安全访问数据

  • 通过输入清理来防止 SQL 注入

  • 灵活的查询功能,包括筛选和范围操作

常见使用案例
  • 根据关系数据要求构建可扩展的应用程序

  • APIs 既需要 GraphQL 灵活性又需要 SQL 数据库功能的创作

  • 通过 GraphQL 突变和查询管理数据操作

  • 实现安全的数据库访问模式

在本教程中,您将学习以下内容。

  • 设置 Aurora 无服务器 v2 集群

  • 启用数据 API 功能

  • 创建和配置数据库结构

  • 为数据库操作定义 GraphQL 架构

  • 为查询和突变实现解析器

  • 通过适当的输入清理来保护您的数据访问安全

  • 通过 GraphQL 接口执行各种数据库操作

设置您的数据库集群

在向添加 Amazon RDS 数据源之前 AWS AppSync,必须先在 Aurora Serverless v2 集群上启用数据 API,然后使用配置密钥AWS Secrets Manager您可以使用以下方法创建 Aurora Serverless v2 集群: AWS CLI

aws rds create-db-cluster \ --db-cluster-identifier appsync-tutorial \ --engine aurora-mysql \ --engine-version 8.0 \ --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \ --master-username USERNAME \ --master-user-password COMPLEX_PASSWORD \ --enable-http-endpoint

这将为集群返回一个 ARN。

创建集群后,必须使用以下命令添加 Aurora Serverless v2 实例。

aws rds create-db-instance \ --db-cluster-identifier appsync-tutorial \ --db-instance-identifier appsync-tutorial-instance-1 \ --db-instance-class db.serverless \ --engine aurora-mysql
注意

这些端点需要一段时间才能激活。您可以在 Amazon RDS 控制台的集群连接和安全选项卡中查看它们的状态。您也可以使用以下 AWS CLI 命令检查集群的状态。

aws rds describe-db-clusters \ --db-cluster-identifier appsync-tutorial \ --query "DBClusters[0].Status"

您可以使用 AWS Secrets Manager 控制台创建 S ecre t,也可以使用上 AWS CLI 一步中的USERNAMECOMPLEX_PASSWORD使用输入文件创建 Secret,如下所示。

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

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

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

这将为密钥返回 ARN。

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

启用数据 API

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

创建数据库和表

在启用数据 API 后,您可以确保它在 AWS CLI中使用 aws rds-data execute-statement 命令。这将确保您的 Aurora 无服务器集群在添加到 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 架构并附加用于执行变更和订阅的解析器。在 AWS AppSync 控制台中创建新 API 并导航到 Schema 页面,然后输入以下内容:

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 创建一个新角色,也可以使用类似于以下内容的策略创建一个角色:

JSON
{ "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 无服务器集群,第二个资源是您的 AR AWS Secrets Manager N。在单击 “创建” 之前,您需要 ARNs 在 AppSync 数据源配置中同时提供两者。

将其作为参数传递给 AWS CLI。

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

这将为密钥返回 ARN。在控制台中创建数据源时,请记下您的 Aurora Serverless 集群的 ARN 和密钥,以备日后使用。 AWS AppSync

构建您的数据库结构

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

aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret" \ --region us-east-1 \ --sql "create DATABASE TESTDB"

如果运行时没有错误,请使用以下 c reate table 命令添加表

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

设计你的 API 接口

在 Aurora Serverless v2 数据 API 启动并运行表后,创建 GraphQL 架构并附加用于执行变更和订阅的解析器。在 AWS 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。对于该角色,您可以 AWS 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:111122223333:cluster:mydbcluster", "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*" ] }, { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": [ "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret", "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*" ] } ] }

请注意,此策略中有两个需要获得角色访问权的语句。第一个资源是你的 Aurora Serverless v2 集群,第二个资源是你的 AR AWS Secrets Manager N。在单击 “创建” 之前,您需要 ARNs 在 AWS AppSync 数据源配置中同时提供两者。

将您的 API 连接到数据库操作

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

  1. 使用 m utation.creat ePet 字段创建一只宠物

  2. 使用 m utation.updat ePet 字段更新宠物

  3. 使用 m utat ion.deletePet 字段删除宠物

  4. 通过 query.g etPet 字段获取单曲

  5. 使用 query. listPets 字段列出所有内容

  6. 使用查询列出价格区间内的宠物。 listPetsByPriceRange字段

Mutation.createPet

在 AWS AppSync 控制台的架构编辑器中,在右侧选择 Att ach Resolver fo createPet(input: CreatePetInput!): Pet r。选择您的 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 语句。结果将以相同的顺序返回。由于这是一种突变,因此您将在插入后运行 s elec t 语句来检索提交的值,以便填充 GraphQL 响应映射模板。

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

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

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

Mutation.updatePet

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

{ "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) } }

响应映射模板部分,添加以下模板。

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

Mutation.deletePet

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

{ "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" } }

响应映射模板部分,添加以下模板。

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

Query.getPet

现在,已经为您的架构创建了突变,请连接三个查询,以展示如何获取单个项目、列表和应用 SQL 过滤。在 AWS AppSync 控制台的架构编辑器中,为其选择附加解析器getPet(id: ID!): Pet选择您的 RDS 数据源。在请求映射模板部分,添加以下模板。

{ "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

在 AWS AppSync 控制台的架构编辑器中,在右侧选择 Att ach Resolver fo getPet(id: ID!): Pet r。选择您的 RDS 数据源。在请求映射模板部分,添加以下模板。

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

响应映射模板部分,添加以下模板。

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

查询。 listPetsByPriceRange

在 AWS AppSync 控制台的架构编辑器中,在右侧选择 Att ach Resolver fo getPet(id: ID!): Pet r。选择您的 RDS 数据源。在请求映射模板部分,添加以下模板。

{ "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])

通过 API 修改您的数据

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

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 } }

利用我们的映射模板where price > :MIN and price < :MAX中的 SQL WH ER E 谓词进行查询。 listPetsByPriceRange使用以下 GraphQL 查询:

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

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

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

保护您的数据访问安全

SQL 注入是数据库应用程序中的一个安全漏洞。当攻击者通过用户输入字段插入恶意 SQL 代码时,就会发生这种情况。这可能允许未经授权访问数据库数据。我们建议您在使用之前仔细验证和清理所有用户输入,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

在你的 Aurora 无服务器 v2 实例中创建它。

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 语句中使用单引号来标记 SQL 语句中字符串字面的开头和结尾,例如 'some string value'。要允许在字符串中使用具有一个或多个单引号字符 ( ') 的字符串值,必须用两个单引号 ('') 替换每个字符串值。例如,如果输入字符串是 Nadia's dog,您可以针对 SQL 语句将其转义,例如

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