Tutorial: Aurora Serverless - AWS AppSync

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Tutorial: Aurora Serverless

O AWS AppSync fornece uma fonte de dados para executar comandos SQL em clusters do Amazon Aurora Sem Servidor que foram ativados com uma API de dados. É possível usar resolvedores do AppSync para executar instruções SQL na API de dados com consultas, mutações e assinaturas do GraphQL.

Criar cluster

Antes de adicionar uma fonte de dados do RDS ao AppSync, você deve primeiro ativar uma API de dados em um cluster do Aurora Serverless e configurar um segredo usando o AWS Secrets Manager. Você pode criar um cluster do Aurora Serverless primeiro com a 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

Isso retornará um ARN para o cluster.

Crie um Segredo por meio do Console do AWS Secrets Manager ou também por meio da CLI com um arquivo de entrada, como o seguinte, usando USERNAME e COMPLEX_PASSWORD da etapa anterior:

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

Passe isso como um parâmetro para a AWS CLI:

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

Isso retornará um ARN para o segredo.

Observe o ARN do cluster do Aurora Serverless e o Segredo para uso posterior no console do AppSync ao criar uma fonte de dados.

Ativar API de dados

Você pode ativar a API de dados em seu cluster seguindo as instruções na documentação do RDS. A API de dados deve ser ativada antes de ser adicionada como uma fonte de dados do AppSync.

Criar banco de dados e tabela

Depois de ativar sua API de dados, você pode garantir que ela funcione com o comando aws rds-data execute-statement na AWS CLI. Isso garantirá que seu cluster do Aurora Serverless esteja configurado corretamente antes de adicioná-lo à API do AppSync. Primeiro crie um banco de dados chamado TESTDB com o parâmetro --sql da seguinte forma:

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"

Se isso for executado sem erro, inclua uma tabela com o comando create table:

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"

Se tudo tiver sido executado sem problemas, você poderá avançar para adicionar o cluster como uma fonte de dados em sua API do AppSync.

Esquema do GraphQL

Agora que sua API de dados do Aurora Serverless está funcionando com uma tabela, criaremos um esquema do GraphQL e anexaremos resolvedores para executar mutações e assinaturas. Crie uma nova API no console do AWS AppSync, navegue até a página Esquema e insira o seguinte:

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 }

Salve seu esquema e navegue até a página Fontes de dados e crie uma nova fonte de dados. Selecione Banco de dados relacional para o tipo de fonte de dados e forneça um nome amigável. Use o nome do banco de dados que você criou na última etapa, bem como o ARN do cluster em que você o criou. Para a Função, você pode fazer com que o AppSync crie uma nova função ou crie uma com uma política semelhante à abaixo:

{ "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:*" ] } ] }

Observe que há duas instruções nesta política que você está concedendo acesso à função. O primeiro recurso é o cluster do Aurora Serverless e o segundo é o ARN do AWS Secrets Manager. Você precisará fornecer OS DOIS ARNs na configuração da fonte de dados do AppSync antes de clicar em Criar.

Configurar resolvedores

Agora que temos um esquema do GraphQL válido e uma fonte de dados RDS, podemos anexar resolvedores aos campos do GraphQL em nosso esquema. Nossa API oferecerá os seguintes recursos:

  1. criar um animal de estimação por meio do campo Mutation.createPet

  2. atualizar um animal de estimação por meio do campo Mutation.updatePet

  3. excluir um animal de estimação por meio do campo Mutation.deletePet

  4. obter um único animal de estimação por meio do campo Query.getPet

  5. listar todos os animais de estimação por meio do campo Query.listPets

  6. listar animais de estimação em uma faixa de preço por meio do campo Query.listPetsByPriceRange

Mutation.createPet

No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para createPet(input: CreatePetInput!): Pet. Selecione a fonte de dados do RDS. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:

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

As instruções SQL serão executadas sequencialmente, com base na ordem na matriz de instruções. Os resultados retornarão na mesma ordem. Como isso é uma mutação, executamos uma instrução select após a insert para recuperar os valores confirmados, a fim de preencher o modelo de mapeamento de resposta do GraphQL.

Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:

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

Como as instruções têm duas consultas SQL, precisamos especificar o segundo resultado na matriz que retorna do banco de dados com: $utils.rds.toJsonString($ctx.result))[1][0]).

Mutation.updatePet

No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para updatePet(input: UpdatePetInput!): Pet. Selecione a fonte de dados do RDS. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:

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

Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:

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

Mutation.deletePet

No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para deletePet(input: DeletePetInput!): Pet. Selecione a fonte de dados do RDS. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:

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

Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:

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

Query.getPet

Agora que as mutações são criadas para o seu esquema, conectaremos as três consultas para mostrar como obter itens individuais, listas e aplicar a filtragem SQL. No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para getPet(id: ID!): Pet. Selecione a fonte de dados do RDS. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:

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

Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:

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

Query.listPets

No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para getPet(id: ID!): Pet. Selecione a fonte de dados do RDS. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:

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

Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:

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

Query.listPetsByPriceRange

No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para getPet(id: ID!): Pet. Selecione a fonte de dados do RDS. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:

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

Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:

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

Executar mutações

Agora que você configurou todos os seus resolvedores com instruções SQL e conectou a API do GraphQL à API de dados do Aurora Serverless, é possível começar a realizar mutações e consultas. No console do AWS AppSync, escolha a aba Consultas e insira o seguinte para criar um animal de estimação:

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

A resposta deve conter o id, o tipo e o preço da seguinte forma:

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

Você pode modificar este item executando a mutação updatePet:

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

Observe que usamos o id que foi retornado da operação createPet anteriormente. Esse será um valor único para o seu registro, já que o resolvedor utilizou $util.autoId(). Você pode excluir um registro de maneira semelhante:

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

Crie alguns registros com a primeira mutação com valores diferentes para preço e execute algumas consultas.

Executar consultas

Ainda na guia Consultas do console, use a seguinte instrução para listar todos os registros que você criou:

query allpets { listPets { id type price } }

Isso é bom, mas vamos aproveitar o predicado SQL WHERE que tinha where price > :MIN and price < :MAX no modelo de mapeamento para Query.listPetsByPriceRange com a seguinte consulta do GraphQL:

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

Você só deve ver registros com um preço acima de US$ 1 ou abaixo de US$ 10. Por fim, é possível realizar consultas para recuperar registros individuais da seguinte maneira:

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

Limpeza de entradas

Recomendamos que os desenvolvedores usem variableMap para proteção contra ataques de injeção de SQL. Se os mapas de variáveis não forem usados, os desenvolvedores serão responsáveis por limpar os argumentos de suas operações do GraphQL. Uma maneira de fazer isso é fornecer etapas de validação específicas de entrada no modelo de mapeamento de solicitação antes da execução de uma instrução SQL em relação à API de dados. Vamos ver como podemos modificar o modelo de mapeamento de solicitação do exemplo listPetsByPriceRange. Em vez de contar apenas com a entrada do usuário, você pode fazer o seguinte:

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

Outra maneira de proteger contra entradas invasivas ao executar resolvedores em relação à API de dados é usar instruções preparadas junto com o procedimento armazenado e entradas parametrizadas. Por exemplo, no resolvedor para listPets, defina o seguinte procedimento que executa select como uma instrução 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

Isso pode ser criado em sua instância do Aurora Serverless usando o seguinte comando sql de execução:

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"

O código de resolvedor resultante para listPets foi simplificado, pois agora simplesmente chamamos o procedimento armazenado. No mínimo, qualquer entrada de string deve ter aspas simples com 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("'", "''")) } }

Escape de strings

As aspas simples representam o início e o fim dos literais de string em uma instrução SQL, por exemplo, 'some string value'. Para permitir que valores de string com um ou mais caracteres de aspas simples (') sejam usados em uma string, cada um deve ser substituído por duas aspas simples (''). Por exemplo, se a string de entrada for Nadia's dog, você deverá inserir caracteres de escape nela para a instrução SQL, como

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