本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
在 Amazon AppSync中使用带有数据 API 的 Aurora PostgreSQL
了解如何使用将你的 GraphQL API 连接到 Aurora PostgreSQL 数据库。 Amazon AppSync这种集成使您能够通过 GraphQL 操作执行 SQL 查询和突变,从而构建可扩展、数据驱动的应用程序。 Amazon AppSync 提供了一个数据源,用于对启用了数据 API 的 Amazon Aurora 集群执行 SQL 语句。您可以使用 Amazon AppSync 解析器通过 GraphQL 查询、突变和订阅对数据 API 运行 SQL 语句。
在开始本教程之前,您应该对 Amazon 服务和 GraphQL 概念有基本的了解。
注意
本教程使用 US-EAST-1
区域。
设置你的 Aurora PostgreSQL 数据库
在向添加 Amazon RDS 数据源之前 Amazon AppSync,请执行以下操作。
在 Aurora 无服务器 v2 集群上启用数据 API。
使用配置密钥 Amazon Secrets Manager
使用以下 Amazon CLI 命令创建集群。
aws rds create-db-cluster \ --db-cluster-identifier appsync-tutorial \ --engine aurora-postgresql \ --engine-version 16.6 \ --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \ --master-username USERNAME \ --master-user-password COMPLEX_PASSWORD \ --enable-http-endpoint
这将为集群返回一个 ARN。创建集群后,必须使用以下 Amazon CLI 命令添加一个 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-postgresql
注意
这些端点需要一段时间才能激活。您可以在 RDS 控制台的集群连接和安全选项卡中查看它们的状态。
使用以下 Amazon CLI 命令检查集群状态。
aws rds describe-db-clusters \ --db-cluster-identifier appsync-tutorial \ --query "DBClusters[0].Status"
通过 Amazon Secrets Manager 控制台创建密钥, Amazon CLI 或者使用上一步中的USERNAME
和输入文件创建密COMPLEX_PASSWORD
钥,如下所示:
{ "username": "USERNAME", "password": "COMPLEX_PASSWORD" }
将其作为参数传递给 Amazon CLI:
aws secretsmanager create-secret \ --name appsync-tutorial-rds-secret \ --secret-string file://creds.json
这将为密钥返回 ARN。在控制台中创建数据源时,请记下 Aurora Serverless v2 集群的 ARN 和密钥,以备日后使用。 Amazon AppSync
创建数据库和表
首先,创建一个名为的数据库TESTDB
。在 PostgreSQL 中,数据库是存放表和其他 SQL 对象的容器。在将 Aurora Serverless v2 集群添加到 API 之前,请 Amazon AppSync 验证其配置是否正确。首先,使用如下--sql
参数创建一个 TESTDB 数据库。
aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \ --sql "create DATABASE \"testdb\"" \ --database "postgres"
如果运行而未出现错误,请使用 create table
命令添加两个表:
aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \ --database "testdb" \ --sql 'create table public.todos (id serial constraint todos_pk primary key, description text not null, due date not null, "createdAt" timestamp default now());' aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \ --database "testdb" \ --sql 'create table public.tasks (id serial constraint tasks_pk primary key, description varchar, "todoId" integer not null constraint tasks_todos_id_fk references public.todos);'
如果成功,请将集群添加为您的 API 中的数据源。
创建 GraphQL 架构
现在,您的 Aurora Serverless v2 数据 API 正在使用已配置的表运行,我们将创建一个 GraphQL 架构。您可以使用 API 创建向导从现有数据库导入表配置,从而快速创建 API。
首先:
-
在 Amazon AppSync 控制台中,选择创建 API,然后选择从 Amazon Aurora 集群开始。
-
指定 API 详细信息,例如 API 名称,然后选择要生成 API 的数据库。
-
选择数据库。如果需要,请更新区域,然后选择您的 Aurora 集群和 TESTDB 数据库。
-
选择您的密钥,然后选择导入。
-
发现表后,更新类型名称。将
Todos
更改为Todo
,并将Tasks
更改为Task
。 -
通过选择预览架构来预览生成的架构。您的架构将如下所示:
type Todo { id: Int! description: String! due: AWSDate! createdAt: String } type Task { id: Int! todoId: Int! description: String }
-
对于该角色,您可以 Amazon AppSync 创建一个新角色,也可以使用类似于以下策略的策略创建一个角色:
请注意,此策略中有两条您要授予角色访问权限的语句。第一个资源是您的 Aurora 集群,第二个资源是您的 Amazon Secrets Manager ARN。
选择下一步,查看配置详细信息,然后选择创建 API。您现在已拥有完全正常运行的 API。您可以在架构页面上查看 API 的完整详细信息。
RDS 的解析器
API 创建流程会自动创建解析器来与我们的类型进行交互。如果你查看 Schema 页面,你会发现解析器有以下一些解析器。
-
通过
Mutation.createTodo
字段创建todo
。 -
通过
Mutation.updateTodo
字段更新todo
。 -
通过
Mutation.deleteTodo
字段删除todo
。 -
通过
Query.getTodo
字段获取单个todo
。 -
通过
Query.listTodos
字段列出所有todos
。
您会发现该 Task
类型附带了类似的字段和解析器。让我们更仔细地看看一些解析器。
Mutation.createTodo
在 Amazon AppSync 控制台的架构编辑器中,选择右testdb
侧的createTodo(...): Todo
。解析器代码使用 rds
模块中的 insert
函数动态创建向 todos
表中添加数据的插入语句。因为我们正在使用 Postgres,所以我们可以利用 returning
语句来取回插入的数据。
更新以下解析器以正确指定due
字段的DATE
类型。
import { util } from '@aws-appsync/utils'; import { insert, createPgStatement, toJsonObject, typeHint } from '@aws-appsync/utils/rds'; export function request(ctx) { const { input } = ctx.args; // if a due date is provided, cast is as `DATE` if (input.due) { input.due = typeHint.DATE(input.due) } const insertStatement = insert({ table: 'todos', values: input, returning: '*', }); return createPgStatement(insertStatement) } export function response(ctx) { const { error, result } = ctx; if (error) { return util.appendError( error.message, error.type, result ) } return toJsonObject(result)[0][0] }
保存解析器。类型提示将输入对象中的 due
正确标记为 DATE
类型。这允许 Postgres 引擎正确解释该值。接下来,更新您的架构以从 CreateTodo
输入中删除 id
。由于我们的 Postgres 数据库可以返回生成的 ID,因此您可以依靠它进行创建,并将结果作为单个请求返回,如下所示。
input CreateTodoInput { due: AWSDate! createdAt: String description: String! }
进行更改并更新您的架构。前往 Querie s 编辑器向数据库中添加一个项目,如下所示。
mutation CreateTodo { createTodo(input: {description: "Hello World!", due: "2023-12-31"}) { id due description createdAt } }
你会得到以下结果。
{ "data": { "createTodo": { "id": 1, "due": "2023-12-31", "description": "Hello World!", "createdAt": "2023-11-14 20:47:11.875428" } } }
Query.listTodos
在控制台的架构编辑器中,在右侧选择 listTodos(id: ID!): Todo
旁边的 testdb
。请求处理程序使用 select 实用程序函数在运行时动态生成请求。
export function request(ctx) { const { filter = {}, limit = 100, nextToken } = ctx.args; const offset = nextToken ? +util.base64Decode(nextToken) : 0; const statement = select({ table: 'todos', columns: '*', limit, offset, where: filter, }); return createPgStatement(statement) }
我们想根据 due
日期筛选 todos
。让我们更新解析器以将 due
值强制转换为 DATE
。按如下方式更新导入列表和请求处理程序。
import { util } from '@aws-appsync/utils'; import * as rds from '@aws-appsync/utils/rds'; export function request(ctx) { const { filter: where = {}, limit = 100, nextToken } = ctx.args; const offset = nextToken ? +util.base64Decode(nextToken) : 0; // if `due` is used in a filter, CAST the values to DATE. if (where.due) { Object.entries(where.due).forEach(([k, v]) => { if (k === 'between') { where.due[k] = v.map((d) => rds.typeHint.DATE(d)); } else { where.due[k] = rds.typeHint.DATE(v); } }); } const statement = rds.select({ table: 'todos', columns: '*', limit, offset, where, }); return rds.createPgStatement(statement); } export function response(ctx) { const { args: { limit = 100, nextToken }, error, result, } = ctx; if (error) { return util.appendError(error.message, error.type, result); } const offset = nextToken ? +util.base64Decode(nextToken) : 0; const items = rds.toJsonObject(result)[0]; const endOfResults = items?.length < limit; const token = endOfResults ? null : util.base64Encode(`${offset + limit}`); return { items, nextToken: token }; }
在查询编辑器中执行以下操作。
query LIST { listTodos(limit: 10, filter: {due: {between: ["2021-01-01", "2025-01-02"]}}) { items { id due description } } }
Mutation.updateTodo
您也可以对 Todo
执行 update
。从查询编辑器中,我们来更新第一个 Todo
项目(id
为 1
)。
mutation UPDATE { updateTodo(input: {id: 1, description: "edits"}) { description due id } }
请注意,您必须指定要更新的项目的 id
。您也可以指定一个条件,以仅更新符合特定条件的项目。例如,如果描述以以下开头,我们可能只想编辑该项目。edits
mutation UPDATE { updateTodo(input: {id: 1, description: "edits: make a change"}, condition: {description: {beginsWith: "edits"}}) { description due id } }
就像我们处理 create
和 list
操作的方式一样,我们可以更新解析器以将 due
字段强制转换为 DATE
。将这些更改保存到,updateTodo
如下所示。
import { util } from '@aws-appsync/utils'; import * as rds from '@aws-appsync/utils/rds'; export function request(ctx) { const { input: { id, ...values }, condition = {}, } = ctx.args; const where = { ...condition, id: { eq: id } }; // if `due` is used in a condition, CAST the values to DATE. if (condition.due) { Object.entries(condition.due).forEach(([k, v]) => { if (k === 'between') { condition.due[k] = v.map((d) => rds.typeHint.DATE(d)); } else { condition.due[k] = rds.typeHint.DATE(v); } }); } // if a due date is provided, cast is as `DATE` if (values.due) { values.due = rds.typeHint.DATE(values.due); } const updateStatement = rds.update({ table: 'todos', values, where, returning: '*', }); return rds.createPgStatement(updateStatement); } export function response(ctx) { const { error, result } = ctx; if (error) { return util.appendError(error.message, error.type, result); } return rds.toJsonObject(result)[0][0]; }
现在尝试使用一个条件进行更新:
mutation UPDATE { updateTodo( input: { id: 1, description: "edits: make a change", due: "2023-12-12"}, condition: { description: {beginsWith: "edits"}, due: {ge: "2023-11-08"}}) { description due id } }
Mutation.deleteTodo
您可以通过 deleteTodo
突变对 Todo
执行 delete
。这就像updateTodo
变异一样,你必须按如下方式指定要删除id
的项目。
mutation DELETE { deleteTodo(input: {id: 1}) { description due id } }
编写自定义查询
我们已经使用 rds
模块实用程序来创建我们的 SQL 语句。我们还可以编写自己的自定义静态语句来与我们的数据库进行交互。首先,更新架构以从 CreateTask
输入中删除 id
字段。
input CreateTaskInput { todoId: Int! description: String }
接下来,创建几个任务。任务的外键关系Todo
如下。
mutation TASKS { a: createTask(input: {todoId: 2, description: "my first sub task"}) { id } b:createTask(input: {todoId: 2, description: "another sub task"}) { id } c: createTask(input: {todoId: 2, description: "a final sub task"}) { id } }
在您的Query
类型中创建一个名getTodoAndTasks
为如下的新字段。
getTodoAndTasks(id: Int!): Todo
按如下方式向该Todo
类型添加一个tasks
字段。
type Todo { due: AWSDate! id: Int! createdAt: String description: String! tasks:TaskConnection }
保存架构。从控制台的架构编辑器中,在右侧为 getTodosAndTasks(id:
Int!): Todo
选择附加解析器。选择您的 Amazon RDS 数据来源。使用以下代码更新您的解析器。
import { sql, createPgStatement,toJsonObject } from '@aws-appsync/utils/rds'; export function request(ctx) { return createPgStatement( sql`SELECT * from todos where id = ${ctx.args.id}`, sql`SELECT * from tasks where "todoId" = ${ctx.args.id}`); } export function response(ctx) { const result = toJsonObject(ctx.result); const todo = result[0][0]; if (!todo) { return null; } todo.tasks = { items: result[1] }; return todo; }
在此代码中,我们使用 sql
标签模板编写了一条 SQL 语句,我们可以在运行时安全地将动态值传递给该语句。createPgStatement
一次最多可以处理两个 SQL 请求。我们用它来为 todo
发送一个查询,并为 tasks
发送另一个查询。您可以使用 JOIN
语句或任何其它方法来完成此操作。其想法是能够编写您自己的 SQL 语句来实现您的业务逻辑。要在查询编辑器中使用查询,请执行以下操作。
query TodoAndTasks { getTodosAndTasks(id: 2) { id due description tasks { items { id description } } } }
删除集群
重要
删除集群是永久性的。在执行此操作之前,请仔细检查您的项目。
删除集群:
$ aws rds delete-db-cluster \ --db-cluster-identifier appsync-tutorial \ --skip-final-snapshot