Uso de resolvedores de pipeline no AWS AppSync
nota
Agora, oferecemos suporte principalmente ao runtime do APPSYNC_JS e sua documentação. Considere usar o runtime do APPSYNC_JS e seus guias disponíveis aqui.
O AWS AppSync fornece uma maneira simples de conectar um campo do GraphQL a uma única fonte de dados por meio de resolvedores de unidade. No entanto, a execução de uma única operação pode não ser suficiente. Os resolvedores de pipeline oferecem a capacidade de executar operações em série mediante fontes de dados. Crie funções na sua API e anexe-as a um resolvedor de pipeline. Cada resultado de execução da função é direcionado para a próxima até que nenhuma função fique sem execução. Com os resolvedores de pipeline, agora você pode criar fluxos de trabalho mais complexos diretamente no AWS AppSync. Neste tutorial, você cria um aplicativo de visualização de fotos simples, no qual os usuários podem publicar e visualizar fotos publicadas por seus amigos.
Configuração com um clique
Se quiser configurar automaticamente o endpoint do GraphQL no AWS AppSync com todos os resolvedores configurados e os recursos necessários da AWS, você pode usar o seguinte modelo do AWS CloudFormation.
Esta pilha cria os seguintes recursos na sua conta:
-
Perfil do IAM para o AWS AppSync para acessar recursos na sua conta
-
2 Tabelas do DynamoDB
-
1 grupo de usuários do Amazon Cognito
-
2 grupos de usuários do Amazon Cognito
-
3 usuários do grupo de usuários do Amazon Cognito
-
1 API do AWS AppSync
No final do processo de criação da pilha do AWS CloudFormation, você recebe um e-mail para cada um dos três usuários do Amazon Cognito que foram criados. Cada e-mail contém uma senha temporária que você usa para fazer login como um usuário do Amazon Cognito no console do AWS AppSync. Salve as senhas para o restante do tutorial.
Configuração manual
Se você preferir passar manualmente por um processo passo a passo pelo console do AWS AppSync, siga o processo de configuração abaixo.
Configurar seus recursos que não são do AWS AppSync
A API se comunica com duas tabelas do DynamoDB: uma tabela pictures que armazena fotos e uma tabela friends que armazena os relacionamentos entre os usuários. A API é configurada para usar o grupo de usuários do Amazon Cognito como tipo de autenticação. A seguinte pilha do CloudFormation configura esses recursos na conta.
No final do processo de criação da pilha do AWS CloudFormation, você recebe um e-mail para cada um dos três usuários do Amazon Cognito que foram criados. Cada e-mail contém uma senha temporária que você usa para fazer login como um usuário do Amazon Cognito no console do AWS AppSync. Salve as senhas para o restante do tutorial.
Criação da API GraphQL
Para criar a API GraphQL no AWS AppSync:
-
Abra o console do AWS AppSync e escolha Criar a partir do zero e escolha Iniciar.
-
Defina o nome da API como
AppSyncTutorial-PicturesViewer. -
Escolha Criar.
O console do AWS AppSync cria uma nova API GraphQL para você usando o modo de autenticação da chave da API. Você pode usar o console para configurar o restante da API GraphQL e executar consultas nela durante o restante desse tutorial.
Configurar a API do GraphQL
Você precisa configurar a API do AWS AppSync com o grupo de usuários do Amazon Cognito que acabou de criar.
-
Escolha a guia Configurações.
-
Na seção Authorization Type, escolha Grupo de usuários do Amazon Cognito.
-
Em Configuração do grupo de usuários, escolha US-WEST-2 para a região da AWS.
-
Escolha o grupo de usuários AppSyncTutorial-UserPool.
-
Escolha DENY como Ação padrão.
-
Deixe o campo Regex do cliente AppId em branco.
-
Escolha Salvar.
Agora, a API está configurada para usar o grupo de usuários do Amazon Cognito como seu tipo de autorização.
Configuração da fonte de dados para as tabelas do DynamoDB
Depois que as tabelas do DynamoDB forem criadas, navegue até a API GraphQL do AWS AppSync no console e selecione a guia Fontes de dados. Agora, você criará uma fonte de dados no AWS AppSync para cada uma das tabelas do DynamoDB que acabou de criar.
-
Escolha a guia Fonte de dados.
-
Selecione Novo para criar uma nova fonte de dados.
-
Para o nome da fonte de dados, insira
PicturesDynamoDBTable. -
Para o tipo de fonte de dados, escolha Tabela do Amazon DynamoDB.
-
Para a região, escolha US-WEST-2.
-
Na lista de tabelas, escolha a tabela AppSyncTutorial-Pictures do DynamoDB.
-
Na seção Criar ou usar um perfil existente, escolha Perfil existente.
-
Escolha o perfil que acabou de ser criada no modelo do CloudFormation. Se você não alterou o ResourceNamePrefix, o nome do perfil deverá ser AppSyncTutorial-DynamoDBRole.
-
Escolha Criar.
Repita o mesmo processo para a tabela friends, o nome da tabela do DynamoDB deve ser AppSyncTutorial-Friends se você não tiver alterado o parâmetro ResourceNamePrefix no momento da criação da pilha do CloudFormation.
Criação do esquema do GraphQL
Agora que as fontes de dados estão conectadas às suas tabelas do DynamoDB, vamos criar um esquema do GraphQL. No editor de esquemas no console do AWS AppSync, verifique se seu esquema corresponde ao esquema a seguir:
schema { query: Query mutation: Mutation } type Mutation { createPicture(input: CreatePictureInput!): Picture! @aws_auth(cognito_groups: ["Admins"]) createFriendship(id: ID!, target: ID!): Boolean @aws_auth(cognito_groups: ["Admins"]) } type Query { getPicturesByOwner(id: ID!): [Picture] @aws_auth(cognito_groups: ["Admins", "Viewers"]) } type Picture { id: ID! owner: ID! src: String } input CreatePictureInput { owner: ID! src: String! }
Escolha Salvar esquema para salvar o esquema.
Alguns dos campos do esquema foram anotados com a diretiva @aws_auth. Como a configuração de ação padrão da API é definida como DENY, a API rejeita todos os usuários que não são membros dos grupos mencionados na diretiva @aws_auth. Para obter mais informações sobre como proteger sua API, você pode ler a página Segurança. Neste caso, somente usuários administradores têm acesso aos campos Mutation.createPicture e Mutation.createFriendship, e os usuários membros dos grupos Administradores ou Visualizadores podem acessar o campo Query.getPicturesByOwner. Todos os outros usuários não têm acesso.
Configurar resolvedores
Agora que você tem um esquema do GraphQL válido e duas fontes de dados, é possível anexar resolvedores aos campos do GraphQL no esquema. A API oferece os seguintes recursos:
-
Crie uma foto por meio do campo Mutation.createPicture
-
Estabeleça a amizade por meio do campo Mutation.createFriendship
-
Recupere uma foto por meio do campo Query.getPicture
Mutation.createPicture
No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para createPicture(input:
CreatePictureInput!): Picture!. Escolha a fonte de dados PicturesDynamoDBTable do DynamoDB. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:
#set($id = $util.autoId()) { "version" : "2018-05-29", "operation" : "PutItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($id), "owner": $util.dynamodb.toDynamoDBJson($ctx.args.input.owner) }, "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input) }
Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:
#if($ctx.error) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result)
A funcionalidade de criação de fotos está concluída. Você está salvando uma foto na tabela Pictures, usando um UUID gerado aleatoriamente como id da foto e usando o nome de usuário do Cognito como proprietário da foto.
Mutation.createFriendship
No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para createFriendship(id:
ID!, target: ID!): Boolean. Escolha a fonte de dados FriendsDynamoDBTable do DynamoDB. Na seção modelo de mapeamento de solicitação, adicione o seguinte modelo:
#set($userToFriendFriendship = { "userId" : "$ctx.args.id", "friendId": "$ctx.args.target" }) #set($friendToUserFriendship = { "userId" : "$ctx.args.target", "friendId": "$ctx.args.id" }) #set($friendsItems = [$util.dynamodb.toMapValues($userToFriendFriendship), $util.dynamodb.toMapValues($friendToUserFriendship)]) { "version" : "2018-05-29", "operation" : "BatchPutItem", "tables" : { ## Replace 'AppSyncTutorial-' default below with the ResourceNamePrefix you provided in the CloudFormation template "AppSyncTutorial-Friends": $util.toJson($friendsItems) } }
Importante: no modelo de solicitação BatchPutItem, o nome exato da tabela do DynamoDB deve estar presente. O nome da tabela padrão é AppSyncTutorial-Friends. Se você estiver usando o nome da tabela incorreto, receberá um erro quando o AppSync tentar assumir o perfil fornecido.
Para simplificar este tutorial, prossiga como se a solicitação de amizade tivesse sido aprovada e salve a entrada de relacionamento diretamente na tabela AppSyncTutorialFriends
Efetivamente, você está armazenando dois itens para cada amizade, pois o relacionamento é bidirecional. Para obter mais detalhes sobre as melhores práticas do Amazon DynamoDB para representar relações de um para muitos (MITM), consulte Melhores práticas do DynamoDB.
Na seção modelo de mapeamento de resposta, adicione o seguinte modelo:
#if($ctx.error) $util.error($ctx.error.message, $ctx.error.type) #end true
Nota: certifique-se de que o seu modelo de solicitação contenha o nome da tabela correta. O nome padrão é AppSyncTutorial-Friends, mas o nome da sua tabela pode ser diferente se você alterou o parâmetro ResourceNamePrefix do CloudFormation.
Query.getPicturesByOwner
Agora que você tem amizades e fotos, precisa fornecer aos usuários a capacidade de ver as fotos de seus amigos. Para atender a esse requisito, você precisa primeiro verificar se o solicitante é amigo do proprietário e, por fim, consultar as fotos.
Como essa funcionalidade requer duas operações de fonte de dados, você criará duas funções. A primeira função, isFriend, verifica se o solicitante e o proprietário são amigos. A segunda função, getPicturesByOwner, recupera as fotos solicitadas com um ID de proprietário. Vejamos o fluxo de execução abaixo para o resolvedor proposto no campo Query.getPicturesByOwner:
-
Antes do modelo de mapeamento: prepare os argumentos de entrada de contexto e campo.
-
Função isFriend: verifica se o solicitante é o proprietário da foto. Caso contrário, ele verifica se os usuários solicitante e proprietário são amigos executando uma operação GetItem do DynamoDB na tabela Friends.
-
Função getPicturesByOwner: recupera fotos da tabela Pictures usando uma operação de consulta do DynamoDB no índice secundário global owner-index.
-
Após o modelo de mapeamento: mapeie o resultado da foto para que os atributos do DynamoDB mapeiem corretamente para os campos esperados do tipo GraphQL.
Primeiro, vamos criar as funções.
Função isFriend
-
Escolha a guia Funções.
-
Escolha Criar função para criar uma função.
-
Para o nome da fonte de dados, insira
FriendsDynamoDBTable. -
Para o nome da função, digite isFriend.
-
Dentro da área de texto do modelo de mapeamento de solicitação, cole o seguinte modelo:
#set($ownerId = $ctx.prev.result.owner) #set($callerId = $ctx.prev.result.callerId) ## if the owner is the caller, no need to make the check #if($ownerId == $callerId) #return($ctx.prev.result) #end { "version" : "2018-05-29", "operation" : "GetItem", "key" : { "userId" : $util.dynamodb.toDynamoDBJson($callerId), "friendId" : $util.dynamodb.toDynamoDBJson($ownerId) } } -
Dentro da área de texto do modelo de mapeamento de resposta, cole o seguinte modelo:
#if($ctx.error) $util.error("Unable to retrieve friend mapping message: ${ctx.error.message}", $ctx.error.type) #end ## if the users aren't friends #if(!$ctx.result) $util.unauthorized() #end $util.toJson($ctx.prev.result) -
Escolha Criar função.
Resultado: você criou a função isFriend.
Função getPicturesByOwner
-
Escolha a guia Funções.
-
Escolha Criar função para criar uma função.
-
Para o nome da fonte de dados, insira
PicturesDynamoDBTable. -
Para o nome da função, digite
getPicturesByOwner. -
Dentro da área de texto do modelo de mapeamento de solicitação, cole o seguinte modelo:
{ "version" : "2018-05-29", "operation" : "Query", "query" : { "expression": "#owner = :owner", "expressionNames": { "#owner" : "owner" }, "expressionValues" : { ":owner" : $util.dynamodb.toDynamoDBJson($ctx.prev.result.owner) } }, "index": "owner-index" } -
Dentro da área de texto do modelo de mapeamento de resposta, cole o seguinte modelo:
#if($ctx.error) $util.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result) -
Escolha Criar função.
Resultado: você criou a função getPicturesByOwner. Agora que as funções foram criadas, anexe um resolvedor de pipeline ao campo Query.getPicturesByOwner.
No editor de esquemas no console do AWS AppSync, à direita, escolha Anexar resolvedor para Query.getPicturesByOwner(id: ID!): [Picture]. Na página seguinte, escolha o link Converter para resolvedor de pipeline exibido abaixo da lista suspensa da fonte de dados. Use o seguinte para o modelo de mapeamento anterior:
#set($result = { "owner": $ctx.args.id, "callerId": $ctx.identity.username }) $util.toJson($result)
Na seção modelo de mapeamento posterior, use o seguinte modelo:
#foreach($picture in $ctx.result.items) ## prepend "src://" to picture.src property #set($picture['src'] = "src://${picture['src']}") #end $util.toJson($ctx.result.items)
Escolha Criar resolvedor. Você anexou seu primeiro resolvedor de pipeline com sucesso. Na mesma página, adicione as duas funções criadas anteriormente. Na seção de funções, escolha Adicionar uma função e escolha ou digite o nome da primeira função, isFriend. Adicione a segunda função seguindo o mesmo processo para a função getPicturesByOwner. Certifique-se de que a função isFriend apareça primeiro na lista, seguida da função getPicturesByOwner. Você pode usar as setas para cima e para baixo para reorganizar a ordem de execução das funções no pipeline.
Agora que o resolvedor de pipeline foi criado e você anexou as funções, vamos testar a API do GraphQL recém-criada.
Teste da API GraphQL
Primeiro, você precisa preencher fotos e amizades executando algumas mutações usando o usuário administrador que você criou. No lado esquerdo do console do AWS AppSync, selecione a guia Consultas.
Mutação createPicture
-
No console do AWS AppSync, escolha a guia Consultas.
-
Escolha Login com grupos de usuários.
-
No modal, insira o exemplo de ID do cliente do Cognito criado pela pilha do CloudFormation (por exemplo, 37solo6mmhh7k4v63cqdfgdg5d).
-
Digite o nome do usuário que você passou como parâmetro para a pilha do CloudFormation. O padrão é nadia.
-
Use a senha temporária que foi enviada para o e-mail que você forneceu como parâmetro para a pilha do CloudFormation (por exemplo, UserPoolUserEmail).
-
Escolha Fazer login. Agora você deve ver o botão renomeado como Sair do perfil nadia ou qualquer nome de usuário escolhido ao criar a pilha do CloudFormation (ou seja, UserPoolUsername).
Vamos enviar algumas mutações createPicture para preencher a tabela de fotos. Execute a seguinte consulta do GraphQL dentro do console:
mutation { createPicture(input:{ owner: "nadia" src: "nadia.jpg" }) { id owner src } }
A resposta deve ter a aparência abaixo:
{ "data": { "createPicture": { "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a", "owner": "nadia", "src": "nadia.jpg" } } }
Vamos adicionar mais algumas fotos:
mutation { createPicture(input:{ owner: "shaggy" src: "shaggy.jpg" }) { id owner src } }
mutation { createPicture(input:{ owner: "rex" src: "rex.jpg" }) { id owner src } }
Você adicionou três fotos usando nadia como usuário administrador.
Mutação createFriendship
Vamos adicionar uma entrada de amizade. Execute as seguintes mutações no console.
Observação: você ainda deve estar conectado como o usuário admin (o usuário admin padrão é nadia).
mutation { createFriendship(id: "nadia", target: "shaggy") }
A resposta deve ter a seguinte aparência:
{ "data": { "createFriendship": true } }
nadia e shaggy são amigos. rex não é amigo de ninguém.
Consulta getPicturesByOwner
Para esta etapa, faça login como o usuário nadia usando os Grupos de usuários do Cognito, com as credenciais configuradas no início desse tutorial. Como usuário nadia, recupere as fotos de propriedade do usuário shaggy.
query { getPicturesByOwner(id: "shaggy") { id owner src } }
Como nadia e shaggy são amigos, a consulta deve retornar a foto correspondente.
{ "data": { "getPicturesByOwner": [ { "id": "05a16fba-cc29-41ee-a8d5-4e791f4f1079", "owner": "shaggy", "src": "src://shaggy.jpg" } ] } }
Da mesma forma, se o usuário nadia tentar recuperar suas próprias fotos, ele também terá êxito. O resolvedor de pipeline foi otimizado para evitar a execução da operação GetItem isFriend nesse caso. Tente a seguinte consulta:
query { getPicturesByOwner(id: "nadia") { id owner src } }
Se você habilitar o registro em log em sua API (no painel Configurações), definir o nível de depuração como TODOS e executar a mesma consulta novamente, ele retornará os logs para a execução do campo. Observando os logs, você pode determinar se a função isFriend retornou anteriormente no estágio Modelo de mapeamento da solicitação:
{ "errors": [], "mappingTemplateType": "Request Mapping", "path": "[getPicturesByOwner]", "resolverArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/types/Query/fields/getPicturesByOwner", "functionArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/functions/o2f42p2jrfdl3dw7s6xub2csdfs", "functionName": "isFriend", "earlyReturnedValue": { "owner": "nadia", "callerId": "nadia" }, "context": { "arguments": { "id": "nadia" }, "prev": { "result": { "owner": "nadia", "callerId": "nadia" } }, "stash": {}, "outErrors": [] }, "fieldInError": false }
A chave earlyReturnedValue representa os dados que foram retornados pela diretiva #return.
Por fim, apesar de o usuário rex ser um membro do Grupo UserPool Viewers do Cognito, como ele não é amigo de ninguém, não poderá acessar nenhuma das fotos de shaggy ou nadia. Se você fizer login como rex no console e executar a seguinte consulta:
query { getPicturesByOwner(id: "nadia") { id owner src } }
Você receberá o seguinte erro não autorizado:
{ "data": { "getPicturesByOwner": null }, "errors": [ { "path": [ "getPicturesByOwner" ], "data": null, "errorType": "Unauthorized", "errorInfo": null, "locations": [ { "line": 2, "column": 9, "sourceName": null } ], "message": "Not Authorized to access getPicturesByOwner on type Query" } ] }
Você implementou com sucesso a autorização complexa usando resolvedores de pipeline.