Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.
Uso de Aurora PostgreSQL con la API de datos en AWS AppSync
AWS AppSync proporciona un origen de datos para ejecutar instrucciones SQL con respecto a clústeres de Amazon Aurora que se han habilitado con una API de datos. Puede utilizar los solucionadores de AWS AppSync para ejecutar instrucciones SQL con respecto a la API de datos con consultas, mutaciones y suscripciones de GraphQL.
nota
En este tutorial se utiliza la región US-EAST-1
.
Creación de clústeres
Antes de añadir un origen de datos de Amazon RDS a AWS AppSync, habilite primero una API de datos en un clúster de Aurora sin servidor. También debe configurar un secreto mediante AWS Secrets Manager. Para crear un clúster de Aurora sin servidor, puede usar la AWS CLI:
aws rds create-db-cluster \ --db-cluster-identifier appsync-tutorial \ --engine aurora-postgresql --engine-version 13.11 \ --engine-mode serverless \ --master-username USERNAME \ --master-user-password COMPLEX_PASSWORD
Esto devolverá un ARN para el clúster. Puede comprobar el estado del clúster con el comando:
aws rds describe-db-clusters \ --db-cluster-identifier appsync-tutorial \ --query "DBClusters[0].Status"
Cree un secreto a través de la consola de AWS Secrets Manager o la AWS CLI con un archivo de entrada como el siguiente utilizando el USERNAME
y la COMPLEX_PASSWORD
del paso anterior:
{ "username": "USERNAME", "password": "COMPLEX_PASSWORD" }
Transfiera esto como parámetro a CLI:
aws secretsmanager create-secret \ --name appsync-tutorial-rds-secret \ --secret-string file://creds.json
Esto devolverá un ARN para el secreto. Anote el ARN de su clúster de Aurora sin servidor y el secreto para su uso posterior al crear un origen de datos en la consola de AWS AppSync.
Habilitación de la API de datos
Cuando el estado del clúster cambie a available
, habilite la API de datos siguiendo la documentación de Amazon RDS. La API de datos debe estar habilitada antes de añadirse como un origen de datos de AWS AppSync. También puede habilitar la API de datos mediante la AWS CLI:
aws rds modify-db-cluster \ --db-cluster-identifier appsync-tutorial \ --enable-http-endpoint \ --apply-immediately
Creación de la base de datos y la tabla
Tras habilitar la API de datos, valide que funciona mediante el comando aws rds-data
execute-statement
en la AWS CLI. Esto garantiza que el clúster de Aurora sin servidor esté configurado correctamente antes de añadirlo a la API de AWS AppSync. En primer lugar, cree una base de datos TESTDB con el parámetro --sql
:
aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:123456789012:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:123456789012:secret:appsync-tutorial-rds-secret" \ --sql "create DATABASE \"testdb\""
Si esto se ejecuta sin errores, añada dos tablas con el comando create table
:
aws rds-data execute-statement \ --resource-arn "arn:aws:rds:us-east-1:123456789012:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:123456789012: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:123456789012:cluster:appsync-tutorial" \ --secret-arn "arn:aws:secretsmanager:us-east-1:123456789012: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);'
Si todo se ejecuta sin problemas, ahora puede añadir el clúster como origen de datos en la API.
Creación de un esquema de GraphQL
Ahora que la API de datos de Aurora sin servidor se ejecuta con tablas configuradas, crearemos un esquema de GraphQL. Puede hacerlo manualmente, pero AWS AppSync le permite empezar rápidamente importando la configuración de la tabla desde una base de datos existente mediante el asistente de creación de la API.
Para empezar:
-
En la consola de AWS AppSync, elija Crear API y, a continuación, Comenzar con un clúster de Amazon Aurora.
-
Especifique los detalles de la API, como el nombre de la API, y, a continuación, seleccione su base de datos para generar la API.
-
Seleccione la base de datos. Si es necesario, actualice la región y, a continuación, elija el clúster de Aurora y la base de datos TESTDB.
-
Elija su secreto y, a continuación, seleccione Importar.
-
Una vez descubiertas las tablas, actualice los nombres de tipos. Cambie
Todos
aTodo
yTasks
aTask
. -
Obtenga una vista previa del esquema generado seleccionando Vista previa del esquema. Sus esquema tendrá un aspecto similar al siguiente:
type Todo { id: Int! description: String! due: AWSDate! createdAt: String } type Task { id: Int! todoId: Int! description: String }
-
Para el rol, puede permitir que AWS AppSync cree un rol nuevo o cree uno con una política similar a la siguiente:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "rds-data:ExecuteStatement", ], "Resource": [ "arn:aws:rds:us-east-1:123456789012:cluster:appsync-tutorial", "arn:aws:rds:us-east-1:123456789012:cluster:appsync-tutorial:*" ] }, { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": [ "arn:aws:secretsmanager:us-east-1:123456789012:secret:your:secret:arn:appsync-tutorial-rds-secret", "arn:aws:secretsmanager:us-east-1:123456789012:secret:your:secret:arn:appsync-tutorial-rds-secret:*" ] } ] }
Tenga en cuenta que hay dos instrucciones en esta política a las que está concediendo acceso de rol. El primer recurso es su clúster de Aurora y el segundo es su ARN de AWS Secrets Manager.
Elija Siguiente, revise los detalles de configuración y, a continuación, elija Crear API. Ahora puede disponer de una API totalmente operativa. Puede revisar todos los detalles de su API en la página Esquema.
Solucionadores para RDS
El flujo de creación de la API creó automáticamente los solucionadores para que interactuaran con nuestros tipos. Si consulta la página Esquema, encontrará los solucionadores necesarios para:
-
Crear un
todo
mediante el campoMutation.createTodo
. -
Actualizar un
todo
mediante el campoMutation.updateTodo
. -
Eliminar un
todo
mediante el campoMutation.deleteTodo
. -
Obtener un
todo
individual mediante el campoQuery.getTodo
. -
Enumerar todos
todos
mediante el campoQuery.listTodos
.
Encontrará campos y solucionadores similares adjuntos para el tipo Task
. Echemos un vistazo más de cerca a algunos de los solucionadores.
Mutation.createTodo
En el editor de esquemas de la consola de AWS AppSync, en el lado derecho, elija testdb
junto a createTodo(...): Todo
. El código de resolución utiliza la función insert
del módulo rds
para crear dinámicamente una instrucción de inserción que añade datos a la tabla todos
. Puesto que estamos trabajando con Postgres, podemos aprovechar la instrucción returning
para recuperar los datos insertados.
Actualicemos el solucionador para especificar correctamente el tipo DATE
del campo due
:
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] }
Guarde el solucionador. La sugerencia de tipo marca el due
correctamente en nuestro objeto de entrada como un tipo DATE
. Esto permite que el motor Postgres interprete correctamente el valor. A continuación, actualice su esquema para eliminar el id
de la entrada CreateTodo
. Puesto que nuestra base de datos de Postgres puede devolver el ID generado, podemos confiar en él para crear y devolver el resultado como una única solicitud:
input CreateTodoInput { due: AWSDate! createdAt: String description: String! }
Realice el cambio y actualice su esquema. Diríjase al editor de consultas para añadir un elemento a la base de datos:
mutation CreateTodo { createTodo(input: {description: "Hello World!", due: "2023-12-31"}) { id due description createdAt } }
Obtiene el resultado:
{ "data": { "createTodo": { "id": 1, "due": "2023-12-31", "description": "Hello World!", "createdAt": "2023-11-14 20:47:11.875428" } } }
Query.listTodos
En el editor de esquemas de la consola, en el lado derecho, elija testdb
junto a listTodos(id: ID!): Todo
. El controlador de solicitudes utiliza la función de utilidad select para crear una solicitud de forma dinámica en tiempo de ejecución.
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) }
Queremos filtrar todos
en función de la fecha due
. Actualicemos la resolución para convertir los valores due
en DATE
. Actualice la lista de importaciones y el controlador de solicitudes:
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 }; }
Vamos a probar la consulta. En el editor de consultas:
query LIST { listTodos(limit: 10, filter: {due: {between: ["2021-01-01", "2025-01-02"]}}) { items { id due description } } }
Mutation.updateTodo
También puede update
a Todo
. En el editor de consultas, vamos a actualizar nuestro primer elemento Todo
de id
1
.
mutation UPDATE { updateTodo(input: {id: 1, description: "edits"}) { description due id } }
Tenga en cuenta que debe especificar el id
del elemento que está actualizando. También puede especificar una condición para actualizar únicamente un elemento que cumpla condiciones específicas. Por ejemplo, es posible que solo queramos editar el elemento si la descripción comienza por edits
:
mutation UPDATE { updateTodo(input: {id: 1, description: "edits: make a change"}, condition: {description: {beginsWith: "edits"}}) { description due id } }
Al igual que gestionamos nuestras operaciones create
y list
, podemos actualizar nuestro solucionador para convertir el campo due
en una DATE
. Guarde estos cambios en 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]; }
Ahora intente realizar una actualización con una condición:
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
Puede delete
un Todo
con la mutación deleteTodo
. Funciona igual que la mutación updateTodo
, y debe especificar el id
del elemento que desea eliminar:
mutation DELETE { deleteTodo(input: {id: 1}) { description due id } }
Escritura de consultas personalizadas
Hemos utilizado las utilidades del módulo rds
para crear nuestras instrucciones SQL. También podemos escribir nuestra propia instrucción estática personalizada para interactuar con nuestra base de datos. En primer lugar, actualice el esquema para eliminar el campo id
de la entrada CreateTask
.
input CreateTaskInput { todoId: Int! description: String }
A continuación, cree un par de tareas. Una tarea tiene una relación de clave externa con 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 } }
Cree un campo nuevo de su tipo Query
denominado getTodoAndTasks
:
getTodoAndTasks(id: Int!): Todo
Añada un campo tasks
al tipo Todo
:
type Todo { due: AWSDate! id: Int! createdAt: String description: String! tasks:TaskConnection }
Guarde el esquema. En el editor de esquemas de la consola, elija a la derecha Asociar solucionador para getTodosAndTasks(id:
Int!): Todo
. Elija el origen de datos de Amazon RDS. Actualice su solucionador con el siguiente código:
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; }
En este código, utilizamos la plantilla de etiquetas de sql
para escribir una instrucción SQL a la que podamos pasar un valor dinámico de forma segura en tiempo de ejecución. createPgStatement
puede aceptar hasta dos solicitudes de SQL a la vez. La usamos para enviar una consulta para nuestra todo
y otra para nuestras tasks
. Podría haberlo hecho con una instrucción JOIN
o con cualquier otro método. La idea es poder escribir su propia instrucción SQL para implementar su lógica empresarial. Para usar la consulta en el editor de consultas, podemos probar lo siguiente:
query TodoAndTasks { getTodosAndTasks(id: 2) { id due description tasks { items { id description } } } }
Eliminación de su clúster
importante
La eliminación de un clúster es permanente. Revise su proyecto detenidamente antes de llevar a cabo esta acción.
Para eliminar el clúster:
$ aws rds delete-db-cluster \ --db-cluster-identifier appsync-tutorial \ --skip-final-snapshot