DEV Community

Cover image for AWS AppSync: Desenvolvimento centrado em código com AWS CloudFormation
Eduardo Rabelo
Eduardo Rabelo

Posted on

AWS AppSync: Desenvolvimento centrado em código com AWS CloudFormation

O AWS AppSync é um serverless GraphQL backend-as-a-service. A maioria dos artigos online mostra seu uso através do console da AWS e configurando via interface web, o que não é uma abordagem centrada em código. Eu gostaria de mostrar uma abordagem orientada a código, empregando uma técnica chamada Infraestrutura como Código usando o AWS CloudFormation. O CloudFormation permite que você especifique todos os recursos da AWS na forma de código e automatize suas implantações por meio da linha de comando. Desta forma, todos os recursos de infraestrutura necessários para o seu aplicativo se tornam parte do repositório git - controlados por versão. Você pode reproduzir a stack inteira (conjunto de recursos relacionados) na nuvem com um único comando, ao invés de repetir as etapas manualmente por meio da GUI. Depois que a stack inicial é criada, você pode implantar facilmente alterações em sua infraestrutura, modificando o arquivo de modelo e, em seguida, executando comandos por meio da linha de comando. Essa abordagem oferece controle total direto por meio de código e é muito flexível para se integrar ao restante do ecossistema da AWS.

Mostrarei passo a passo como criar e implantar uma API AppSync GraphQL de nível de produção usando o DynamoDB, tudo a partir do conforto de seu editor e terminal.

1. Definindo o esquema GraphQL

Iremos criar um esquema de exemplo com uma consulta para obter contatos e uma mutação para adicionar um novo contato e salvar o arquivo como schema.graphql no diretório principal do projeto.

mkidr exemplo-appsync
cd exemplo-appsync
touch schema.graphql
vim schema.graphql

Adicionamos:

type Contact {
  username: String!
  fullName: String!
  email: String!
  phone: String
}

input ContactInput {
  fullName: String!
  email: String!
  phone: String
}

type Query {
  getContacts: [Contact!]!
}

type Mutation {
  addContact(username: String! contact: ContactInput!): Contact
}

type Schema {
  query: Query
  mutation: Mutation
}

2. Adicionar modelos de mapeamento para os Resolvers

O modelo de mapeamento de resolução informa ao AppSync como converter uma solicitação de GraphQL recebida em instruções para sua fonte de dados do back-end e como traduzir a resposta dessa origem de dados de volta para uma resposta do GraphQL. Criaremos modelos de mapeamento de solicitação e resposta separados em arquivos diferentes para cada resolver.

$ mkdir mapping-templates
$ cd mapping-templates
$ touch addContact.request.vm
$ touch addContact.response.vm
$ touch getContacts.request.vm
$ touch getContacts.response.vm

Em addContact.request.vm, adicione:

## mapeamento de resolução da requisição da mutação `addContact`, mapeando para a operação `PutItem` do DynamoDB.
{
  "version": "2017-02-28",
  "operation": "PutItem",
  "key": {
    "username": $util.dynamodb.toDynamoDBJson($ctx.args.username)
  },
  "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.contact)
}

Em addContact.response.vm, adicione:

## mapeamento de resolução da resposta da mutação `addContact`, retornando o novo objeto de contato adicionado.
$util.toJson($ctx.result)

Em getContacts.request.vm, adicione:

## mapeamento de resolução da requisição da query `getContacts`, mapeando para a operação `Scan` do DynamoDB.
{
  "version": "2017-02-28",
  "operation": "Scan"
}

Em getContacts.response.vm, adicione:

## mapeamento de resolução da resposta da query `getContacts`, retornando todos os contatos.
$util.toJson($ctx.result.items)

3. Especifique recursos no modelo do CloudFormation

Aqui criaremos o modelo CloudFormation no formato YAML, que é mais legível do que sua contraparte JSON. Ele especifica detalhes sobre nossa AppSync API, API Key, o esquema GraphQL, a tabela do DynamoDB, nossas Fontes de Dados para a API AppSync, os Resolvers do GraphQL e o acesso à Tabela do DynamoDB usando uma IAM Role.

$ touch template.yml
$ vim template.yml

E em seguida:

AWSTemplateFormatVersion: '2010-09-09'
Description: Create AppSync GraphQL API using DynamoDB

Parameters:

  ApiName:
    Type: String
    Description: Name of the API - used to generate unique names for resources
    MinLength: 3
    MaxLength: 25
    AllowedPattern: '^[a-zA-Z][a-zA-Z0-9\-]*$'

Resources:

  GraphQLApi:
    Type: AWS::AppSync::GraphQLApi
    Properties:
      Name: !Sub ${ApiName}-graphql-api
      AuthenticationType: API_KEY

  ApiKey:
    Type: AWS::AppSync::ApiKey
    Properties:
      ApiId: !GetAtt GraphQLApi.ApiId

  GraphQLSchema:
    Type: AWS::AppSync::GraphQLSchema
    Properties:
      ApiId: !GetAtt GraphQLApi.ApiId
      # caminho para o esquema graphql
      DefinitionS3Location: schema.graphql

  ContactsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${ApiName}-contacts
      KeySchema:
      - AttributeName: username
        KeyType: HASH
      AttributeDefinitions:
      - AttributeName: username
        AttributeType: S
      BillingMode: PAY_PER_REQUEST

  ApiRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - appsync.amazonaws.com
          Action:
          - sts:AssumeRole
      Policies:
      - PolicyName: !Sub ${ApiName}-dynamo-access
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:DeleteItem
            - dynamodb:UpdateItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            Resource: !GetAtt ContactsTable.Arn

  ContactsTableDataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      Name: ContactsTableDataSource
      Type: AMAZON_DYNAMODB
      ServiceRoleArn: !GetAtt ApiRole.Arn
      DynamoDBConfig:
        AwsRegion: !Ref AWS::Region
        TableName: !Ref ContactsTable
      ApiId: !GetAtt GraphQLApi.ApiId

  QueryGetContactsResolver:
    Type: AWS::AppSync::Resolver
    Properties:
      TypeName: Query
      FieldName: getContacts
      DataSourceName: !GetAtt ContactsTableDataSource.Name
      # caminho para o arquivo de mapeamento de resolução da requisição
      RequestMappingTemplateS3Location: mapping-templates/getContacts.request.vm
      # caminho para o arquivo de mapeamento de resolução da resposta
      ResponseMappingTemplateS3Location: mapping-templates/getContacts.response.vm
      ApiId: !GetAtt GraphQLApi.ApiId

  MutationAddContactResolver:
    Type: AWS::AppSync::Resolver
    Properties:
      TypeName: Mutation
      FieldName: addContact
      DataSourceName: !GetAtt ContactsTableDataSource.Name
      # caminho para o arquivo de mapeamento de resolução da requisição
      RequestMappingTemplateS3Location: mapping-templates/addContact.request.vm
      # caminho para o arquivo de mapeamento de resolução da resposta
      ResponseMappingTemplateS3Location: mapping-templates/addContact.response.vm
      ApiId: !GetAtt GraphQLApi.ApiId

Outputs:

  GraphQLApiEndpoint:
    Description: GraphQL API URL
    Value: !GetAtt GraphQLApi.GraphQLUrl

  ApiKey:
    Description: API Key
    Value: !GetAtt ApiKey.ApiKey

4. Deploy usando o AWS CLI

Primeiro, instale o AWS CLI e configure-o especificando as credenciais de segurança e as informações de região da sua conta. Forneça a opção de perfil em todos os comandos se você quiser usar uma configuração de perfil específica, caso contrário, usará o perfil padrão.

Crie um bucket do S3 para fazer o upload de arquivos de código:

aws s3 mb s3://contacts-dev-codes  --profile faisal

Execute o comando package para empacotar os artefatos locais referenciados no arquivo CloudFormation que criamos, template.yml, para o bucket S3 especificado. Isso gerará um novo arquivo, que se irá usar referências aos arquivos no bucket S3 ao invés dos arquivos locais.

aws cloudformation package \
    --s3-bucket contacts-dev-codes \
    --template-file template.yml \
    --output-template-file packaged-template.yml \
    --profile faisal

Execute o comando deploy utilizando o arquivo recém-gerado para criar todos os recursos de infraestrutura. Além disso, não esqueça de fornecer o nome da stack e os parâmetros necessários do template:

aws cloudformation deploy \
    --stack-name contacts-dev \
    --template-file packaged-template.yml \
    --capabilities CAPABILITY_IAM \
    --parameter-overrides ApiName=contacts-dev \
    --profile faisal

Agora você tem uma API AppSync GraphQL totalmente funcional com DynamoDB na AWS. Você pode usar os comandos package e deploy repetidamente sempre que fizer alterações no template, ele criará automaticamente o conjunto de mudanças e o executará. Se você estiver usando o Node.js, poderá adicionar esses comandos como scripts do NPM ao package.json de um projeto, por conveniência.

Eu criei o repositório GitHub que contém todo o código padrão com a configuração completa necessária para o kickstart do projeto AppSync.

Crédito

Top comments (4)

Collapse
 
heldr profile image
Helder Santana

Tenho usado o AWS Amplify que automatiza bastante coisa no CloudFormation, mas interessante saber como funciona debaixo dos panos.

Exemplo legal usando Amplify que postaram dias atrás:
aws.amazon.com/blogs/mobile/amplif...

Parabéns por sempre compartilhar conteúdo de qualidade com a comunidade BR.

Collapse
 
oieduardorabelo profile image
Eduardo Rabelo • Edited

obrigado pelo comentário Helder!

ao longo do tempo eu aprendi a gosta do CloudFormation. A documentação não é das melhores, mas tem muito conteúdo da AWS sobre, que ajuda bastante.

eu to para escrever um tutorial de como escrever um projeto AppSync com CloudFormation Stacks, que seria um modo prático de modularizar o projeto para produção e utilizar Output->Input entre stacks.

faz mais de meses que tá parado, por falta de tempo mesmo hahaha,

mas depois que você pega a prática e monta alguns templates, fica muito fácil usar CloudFormation puro.

aqui onde trabalho usamos cfn para tudo no lado da infra e apis, apenas client-side apps usam alguma abstração, tais como serverless framework, sam, amplify etc.

alguns plugins que sempre vejo por aqui:

Collapse
 
rehanvdm profile image
Rehan van der Merwe

Hmm English version?

Collapse
 
oieduardorabelo profile image
Eduardo Rabelo • Edited

follow the link in the last section "Crédito", the title is in en