Uso de Aurora Postgre SQL con datos en API AWS AppSync - AWS AppSync

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 Postgre SQL con datos en API AWS AppSync

AWS AppSync proporciona una fuente de datos para ejecutar SQL sentencias en los clústeres de Amazon Aurora que están habilitados con un DataAPI. Puedes usar AWS AppSync resolutores para ejecutar SQL sentencias con los datos API 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 una fuente de RDS datos de Amazon AWS AppSync, habilite primero un clúster de Data API on Aurora Serverless. 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á una 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 AWS Secrets Manager consola o AWS CLI con un archivo de entrada como el siguiente mediante USERNAME y COMPLEX_PASSWORD desde el paso anterior:

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

Pase esto como parámetro aCLI:

aws secretsmanager create-secret \ --name appsync-tutorial-rds-secret \ --secret-string file://creds.json

Esto devolverá un formulario ARN para el secreto. Tome nota del clúster y ARN del secreto de Aurora Serverless para más adelante cuando cree una fuente de datos en la AWS AppSync consola.

Habilitar los datos API

Una vez que el estado del clúster cambie aavailable, habilite los datos API siguiendo la RDSdocumentación de Amazon. Los datos API deben estar habilitados antes de añadirlos como fuente AWS AppSync de datos. También puede habilitar los datos API mediante 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

Después de habilitar sus datosAPI, compruebe que funcionan con el aws rds-data execute-statement comando de AWS CLI. Esto garantiza que el clúster Aurora Serverless esté configurado correctamente antes de añadirlo al AWS AppSync API. En primer lugar, cree una TESTDBbase de datos con el --sql parámetro:

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 funciona sin problemas, ahora puede añadir el clúster como fuente de datos en suAPI.

Creación de un esquema de GraphQL

Ahora que sus datos sin servidor de Aurora API se están ejecutando 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 API creación.

Para empezar:

  1. En la AWS AppSync consola, seleccione Crear yAPI, a continuación, Iniciar con un clúster de Amazon Aurora.

  2. Especifique API detalles como el APInombre y, a continuación, seleccione su base de datos para generarlaAPI.

  3. Seleccione la base de datos. Si es necesario, actualice la región y, a continuación, elija el clúster y la TESTDBbase de datos de Aurora.

  4. Elija su secreto y, a continuación, seleccione Importar.

  5. Una vez descubiertas las tablas, actualice los nombres de tipos. Cambie Todos a Todo y Tasks a Task.

  6. 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 }
  7. Para el rol, puede AWS AppSync crear uno nuevo o 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 el clúster de Aurora y el segundo es el suyo AWS Secrets Manager ARN.

    Elija Siguiente, revise los detalles de configuración y, a continuación, elija Crear API. Ahora tiene una totalmente operativaAPI. Puedes revisar todos los detalles del tuyo API en la página del esquema.

Resolvedores para RDS

El flujo API de creación creó automáticamente los resolutores para que interactuaran con nuestros tipos. Si consulta la página Esquema, encontrará los solucionadores necesarios para:

  • Crear un todo mediante el campo Mutation.createTodo.

  • Actualizar un todo mediante el campo Mutation.updateTodo.

  • Eliminar un todo mediante el campo Mutation.deleteTodo.

  • Obtener un todo individual mediante el campo Query.getTodo.

  • Enumerar todos todos mediante el campo Query.listTodos.

Encontrará campos y solucionadores similares adjuntos para el tipo Task. Echemos un vistazo más de cerca a algunos de los solucionadores.

Mutación. createTodo

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, selecciona testdb junto acreateTodo(...): 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" } } }

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

Mutación. 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 } }

Mutación. 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 rds módulo para crear nuestras SQL declaraciones. 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. Elige tu fuente de RDS datos de Amazon. 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 sql etiquetas para escribir una SQL declaración a la que podamos pasar un valor dinámico de forma segura en tiempo de ejecución. createPgStatementpuede aceptar hasta dos SQL solicitudes 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 tu propia SQL declaración para implementar tu 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