Tutorial: Aurora sin servidor - 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.

Tutorial: Aurora sin servidor

AWS AppSync proporciona un origen de datos para ejecutar comandos SQL con respecto a clústeres de Amazon Aurora sin servidor que se han habilitado con una API de datos. Puede utilizar los solucionadores de AppSync para ejecutar instrucciones de SQL con respecto a la API de datos con consultas, mutaciones y suscripciones de GraphQL.

Crear un clúster

Antes de añadir un origen de datos de RDS a AppSync primero debe habilitar una API de datos en un clúster de Aurora sin servidor y configurar un secreto con AWS Secrets Manager. Puede crear un clúster de Aurora sin servidor primero con AWS CLI:

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

Esto devolverá un ARN para el clúster.

Cree un secreto a través de la consola de AWS Secrets Manager o también a través de la interfaz de línea de comandos (CLI) con un archivo de entrada como el siguiente utilizando los valores de USERNAME y COMPLEX_PASSWORD del paso anterior:

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

Transfiera esto como parámetro a la AWS CLI:

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

Esto devolverá un ARN para el secreto.

Anote el ARN de su clúster de Aurora Serverless y secreto para su uso posterior en la consola de AppSync a la hora de crear un origen de datos.

Habilitar la API de datos

Puede habilitar la API de datos en el clúster siguiendo las instrucciones de la documentación de RDS. La API de datos debe estar habilitada antes de añadirse como un origen de datos de AppSync.

Creación de una base de datos y tabla

Una vez que tenga habilitada su API de datos, puede asegurarse de que funciona con el comando aws rds-data execute-statement de la AWS CLI. Esto garantizará que el clúster de Aurora sin servidor esté configurado correctamente antes de añadirlo a la API de AppSync. En primer lugar, cree una base de datos denominada TESTDB con el parámetro --sql del siguiente modo:

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"

Si esto se ejecuta sin errores, añada una tabla con el comando de creación de tablas:

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"

Si todo se ha ejecutado sin problemas puede avanzar para añadir el clúster como origen de datos en la API de AppSync.

Esquema de GraphQL

Ahora que su API de datos de Aurora Serverless está en marcha con una tabla, vamos a crear un esquema GraphQL y asociar los solucionadores para realizar mutaciones y suscripciones. Cree una nueva API en la consola de AWS AppSync, vaya a la página Esquema y escriba lo siguiente:

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 }

Guarde su esquema y vaya a la página Data Sources (Orígenes de datos) y cree un nuevo origen de datos. Seleccione la Relational database (Base de datos relacional) para el tipo de origen de datos y proporcione un nombre fácil de recordar. Utilice el nombre de la base de datos que ha creado en el último paso, así como el Cluster ARN (ARN del clúster) en el que lo creó. Para el Role (Rol) puede permitir que 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: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:*" ] } ] }

Tenga en cuenta que hay dos Statements (Instrucciones) en esta política a las que está concediendo acceso de rol. El primer Recurso es su clúster de Aurora sin servidor y el segundo es su ARN de AWS Secrets Manager. Tendrá que proporcionar AMBOS ARN en la configuración del origen de datos de AppSync antes de hacer clic en Create (Crear).

Configuración de solucionadores

Ahora que tenemos un esquema de GraphQL válido y un origen de datos de RDS, podemos asociar los solucionadores a los campos de GraphQL en el esquema. Nuestra API ofrecerá las siguientes capacidades:

  1. crear una mascota a través del campo Mutation.createPet

  2. actualizar una mascota a través del campo Mutation.updatePet

  3. eliminar una mascota a través del campo Mutation.deletePet

  4. obtener una mascota única a través del campo Query.getPet

  5. enumerar todas las mascotas a través del campo Query.listPets

  6. enumerar las mascotas en un rango de precios a través del campo Query.listPetsByPriceRange

Mutation.createPet

En el editor de esquemas de la consola de AWS AppSync, elija a la derecha Asociar solucionador para createPet(input: CreatePetInput!): Pet. Elija el origen de los datos de RDS. En la sección request mapping template (plantilla de mapeo de solicitudes), añada la siguiente plantilla:

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

Las instrucciones SQL se ejecutarán de forma secuencial, en función del orden de la matriz statements (instrucciones). Los resultados volverán en el mismo orden. Dado que se trata de una mutación, ejecutamos una instrucción select (selección) después de insert (insertar) para recuperar los valores comprometidos para rellenar la plantilla de mapeo de respuesta de GraphQL.

En la sección Plantilla de mapeo de respuestas, añada la siguiente plantilla:

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

Debido a que las statements (instrucciones) tienen dos consultas SQL, necesitamos especificar el segundo resultado en la matriz que viene de la base de datos con: $utils.rds.toJsonString($ctx.result))[1][0]).

Mutation.updatePet

En el editor de esquemas de la consola de AWS AppSync, elija a la derecha Asociar solucionador para updatePet(input: UpdatePetInput!): Pet. Elija el origen de los datos de RDS. En la sección Plantilla de mapeo de solicitudes, añada la siguiente plantilla:

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

En la sección Plantilla de mapeo de respuestas, añada la siguiente plantilla:

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

Mutation.deletePet

En el editor de esquemas de la consola de AWS AppSync, elija a la derecha Asociar solucionador para deletePet(input: DeletePetInput!): Pet. Elija el origen de los datos de RDS. En la sección Plantilla de mapeo de solicitudes, añada la siguiente plantilla:

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

En la sección Plantilla de mapeo de respuestas, añada la siguiente plantilla:

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

Query.getPet

Ahora que las mutaciones se han creado para su esquema, conectaremos las tres consultas para mostrar cómo obtener elementos individuales, listas y aplicar el filtrado de SQL. En el editor de esquemas de la consola de AWS AppSync, elija a la derecha Asociar solucionador para getPet(id: ID!): Pet. Elija el origen de los datos de RDS. En la sección Plantilla de mapeo de solicitudes, añada la siguiente plantilla:

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

En la sección Plantilla de mapeo de respuestas, añada la siguiente plantilla:

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

Query.listPets

En el editor de esquemas de la consola de AWS AppSync, elija a la derecha Asociar solucionador para getPet(id: ID!): Pet. Elija el origen de los datos de RDS. En la sección Plantilla de mapeo de solicitudes, añada la siguiente plantilla:

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

En la sección Plantilla de mapeo de respuestas, añada la siguiente plantilla:

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

Query.listPetsByPriceRange

En el editor de esquemas de la consola de AWS AppSync, elija a la derecha Asociar solucionador para getPet(id: ID!): Pet. Elija el origen de los datos de RDS. En la sección Plantilla de mapeo de solicitudes, añada la siguiente plantilla:

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

En la sección Plantilla de mapeo de respuestas, añada la siguiente plantilla:

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

Ejecutar mutaciones

Ahora que ha configurado todos sus solucionadores con instrucciones SQL y conectado API de GraphQL a su API de datos Aurora Serverless, puede comenzar a realizar mutaciones y consultas. En la consola de AWS AppSync, elija la pestaña Consultas e introduzca lo siguiente para crear una mascota:

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

La respuesta debe contener los valores de id, type (tipo) y price (precio) de la siguiente manera:

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

Puede modificar este elemento mediante la ejecución de la mutación updatePet:

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

Tenga en cuenta que hemos usado el id que ha devuelto la operación createPet anteriormente. Este será un valor único para su registro, ya que el solucionador obtuvo $util.autoId(). Podría eliminar un registro de un modo similar:

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

Cree un par de registros con la primera mutación con valores diferentes de price (precio) y, a continuación, ejecute algunas consultas.

Ejecutar consultas

En la pestaña Queries (Consultas) de la consola, utilice la siguiente instrucción para obtener una lista de todos los registros que haya creado:

query allpets { listPets { id type price } }

Esto está bien, pero vamos a aprovechar el predicado de SQL WHERE (DÓNDE) que tenía where price > :MIN and price < :MAX en nuestra plantilla de mapeo para Query.listPetsByPriceRange con la siguiente consulta GraphQL:

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

Solo debe ver registros con un price (precio) superior a 1 USD o inferior a 10 USD. Por último, puede realizar consultas para obtener registros individuales tal y como se indica a continuación:

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

Saneamiento de la entrada

Recomendamos a los desarrolladores que utilicen variableMap como protección contra los ataques de inyección de código SQL. Si no se utilizan mapas de variables, los desarrolladores son responsables de sanear los argumentos de sus operaciones de GraphQL. Una forma de hacerlo es proporcionando los pasos de validación específicos de entrada en la plantilla de mapeo de solicitudes antes de la ejecución de una instrucción SQL con respecto a la API de datos. Veamos cómo podemos modificar la plantilla de mapeo de solicitudes del ejemplo de listPetsByPriceRange. En lugar de confiar solamente en la entrada del usuario puede hacer lo siguiente:

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

Otra forma de protegerse frente a la entrada no autorizada a la hora de ejecutar solucionadores con respecto a la API de datos es utilizar las instrucciones preparadas junto con el procedimiento almacenado y las entradas parametrizadas. Por ejemplo, en el solucionador para listPets defina el siguiente procedimiento que ejecuta select (seleccionar) como una instrucción preparada:

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

Esto se puede crear en su instancia Aurora Serverless utilizando el siguiente comando SQL de ejecución:

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"

El código de solucionador resultante para listPets se simplifica, ya que ahora simplemente llamamos al procedimiento almacenado: Como mínimo, toda entrada de cadena debe tener comillas simples con caracteres de escape.

#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("'", "''")) } }

Escapar cadenas

Las comillas simples representan el inicio y el final de los literales de cadena en una instrucción SQL; por ejemplo, 'some string value'. Para permitir el uso de valores de cadena con uno o más caracteres de comillas simples (') dentro de una cadena, cada uno de ellos debe reemplazarse por dos comillas simples (''). Por ejemplo, si la cadena de entrada es Nadia's dog, para la instrucción SQL se aplicarían caracteres de escape de la siguiente forma:

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