Neste módulo, você provisionará um banco de dados Amazon DynamoDB e aprenderá a usar o DynamoDB para armazenar informações sobre o jogo baseado em rodadas.

Tempo de conclusão do módulo: 30 minutos


O Amazon DynamoDB é um banco de dados NoSQL totalmente gerenciado fornecido pela AWS. Ele fornece tempos de resposta inferiores a 10 milissegundos e escalabilidade quase infinita. O DynamoDB é usado por inúmeros aplicativos e setores, do carrinho de compras da Amazon.com ao serviço de geolocalização da Lyft e a uma variedade de jogos on-line.

Toda interação com o DynamoDB é realizada por HTTPS com o AWS Identity and Access Management (IAM) para autenticação e autorização. Normalmente, você usa o AWS SDK no idioma de sua escolha para interagir com o DynamoDB. Se estiver usando opções de computação da AWS para o seu aplicativo, como Amazon Elastic Compute Cloud (Amazon EC2) ou AWS Lambda, o aplicativo poderá usar as credenciais da AWS em seu ambiente computacional para enviar solicitações ao DynamoDB.

Nas próximas etapas , você primeiramente provisionará um banco de dados DynamoDB. Depois você aprenderá a interagir com o banco de dados DynamoDB usando o AWS SDK para JavaScript no Node.js.


  • Etapa 1. Provisione um banco de dados Amazon DynamoDB

    Vamos começar com o provisionamento de um banco de dados DynamoDB. Um banco de dados no DynamoDB também é chamado de tabela.

    Ao criar uma tabela do DynamoDB, você precisa especificar o(s) atributo(s) que comporá(ão) a chave primária da tabela. Cada registro que você grava na tabela do DynamoDB chama-se item, e cada item deve incluir a chave primária da tabela.

    O projeto de chave primária e modelagem de dados do DynamoDB é um tópico importante. Contudo, seu jogo tem um padrão de acesso simples que não requer modelagem avançada de dados. Por esse motivo, este tutorial não abordará tópicos avançados da modelagem de dados do DynamoDB. Para obter detalhes adicionais sobre modelagem de dados no DynamoDB, leia os tutoriais sobre como Projetar um banco de dados para um aplicativo móvel com o Amazon DynamoDB ou Modelar dados de um aplicativo de jogos com o Amazon DynamoDB.

    Em seu aplicativo, use o DynamoDB como um armazenamento simples de chave-valor. Os jogos têm um atributo gameId que identifica exclusivamente cada jogo. O atributo gameId é usado como a chave primária da tabela.

    No diretório scripts/, há um arquivo chamado create-table.sh que cria a tabela do DynamoDB com a AWS Command Line Interface, AWS CLI (interface de linha de comando da AWS). Este é o conteúdo do arquivo:
     

    aws dynamodb create-table \
      --table-name turn-based-game \
      --attribute-definitions '[
        {
          "AttributeName": "gameId",
          "AttributeType": "S"
        }
      ]' \
      --key-schema '[
        {
          "AttributeName": "gameId",
          "KeyType": "HASH"
        }
      ]' \
      --provisioned-throughput '{
        "ReadCapacityUnits": 5,
        "WriteCapacityUnits": 5
      }'

    Primeiramente, nomeie a tabela como turn-based-game. Então ela declara os atributos que são usados na chave primária da tabela. Neste exemplo, é usada uma chave primária simples. Por isso, declare um único atributo, gameId, que é um tipo de dados string. Em seguida, para especificar o esquema de chave primária, defina o atributo gameId para ser usado como a chave hash da tabela.

    Por fim, especifique o número de unidades de capacidade de leitura e gravação que usará na tabela. O DynamoDB tem um modelo de definição de preço sob demanda: você paga por cada solicitação de leitura e gravação na tabela. Porém, como o modo de throughput provisionado se enquadra no nível gratuito da AWS, você pode usá-lo aqui.

    Execute o seguinte comando no terminal para criar sua tabela:

    bash scripts/create-table.sh

    A seguinte saída será exibida no terminal:

    {
        "TableDescription": {
            "AttributeDefinitions": [
                {
                    "AttributeName": "gameId",
                    "AttributeType": "S"
                }
            ],
            "TableName": "turn-based-game",
            "KeySchema": [
                {
                    "AttributeName": "gameId",
                    "KeyType": "HASH"
                }
            ],
            "TableStatus": "CREATING",
            "CreationDateTime": 1574086642.07,
            "ProvisionedThroughput": {
                "NumberOfDecreasesToday": 0,
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
            },
            "TableSizeBytes": 0,
            "ItemCount": 0,
            "TableArn": "arn:aws:dynamodb:us-east-1:955617200811:table/turn-based-game",
            "TableId": "c62cb86a-211e-4a50-a160-4a616c8f3445"
        }
    }
  • Etapa 2. Salve um exemplo de item de jogo na tabela

    Agora que a tabela está criada, você pode adicionar itens à tabela. Cada jogo é representado por um único item na tabela.

    Este é o esquema de cada item de jogo:

    Cada jogo inclui um GameId, que é o identificador exclusivo do jogo. Os atributos User1 e User2 armazenam os nomes de usuário dos dois usuários que participam do jogo. Os atributos Heap1, Heap2 e Heap3 armazenam o número de objetos em cada uma das três pilhas. Finalmente, o atributo LastMoveBy indica o jogador que fez a jogada mais recente.

    No diretório scripts/, há um arquivo createGame.js que adiciona um exemplo de jogo à tabela. Este é o conteúdo do arquivo:

    const AWS = require('aws-sdk')
    const documentClient = new AWS.DynamoDB.DocumentClient()
    
    const params = {
      TableName: 'turn-based-game',
      Item: {
        gameId: '5b5ee7d8',
        user1: 'myfirstuser',
        user2: 'theseconduser',
        heap1: 5,
        heap2: 4,
        heap3: 5,
        lastMoveBy: 'myfirstuser'
      }
    }
    
    documentClient.put(params).promise()
      .then(() => console.log('Game added successfully!'))
      .catch((error) => console.log('Error adding game', error))

    Importe o AWS SDK e crie uma instância do DynamoDB Document Client. O Document Client é uma abstração de alto nível na API do DynamoDB de baixo nível e facilita o trabalho com itens do DynamoDB. Após a criação do cliente, o script monta os parâmetros de uma chamada da API PutItem, incluindo o nome da tabela e os atributos no item. Em seguida, ele chama o método put() no Document Client e registra informações sobre êxito ou falha.

    Para executar o script a fim de inserir um jogo na tabela , execute o seguinte comando no terminal:

    node scripts/createGame.js

    A seguinte saída será exibida no terminal:

    Game added successfully!

    Observação: se você executar o comando rápido demais, poderá ser exibida uma mensagem de erro informando que a tabela ainda não está disponível. Aguarde um minuto e tente o comando novamente.

    Ótimo! Você adicionou um jogo à tabela. Na próxima etapa, você aprenderá a atualizar esse item de jogo para simular a jogada de um usuário.

  • Etapa 3. Atualize um item de jogo na tabela

    Agora que você tem um item jogo na tabela, pode aprender a simular a jogada de um jogador em um jogo em andamento.

    Há duas maneiras de executar essa operação. Na primeira, recupere o item usando a API GetItem. Depois atualize o item de jogo no aplicativo de acordo com a jogada realizada pelo jogador. Por fim, substitua o item existente no DynamoDB usando a API PutItem. Embora essa opção funcione, ela requer várias solicitações à tabela do DynamoDB e há riscos de sobregravar as alterações efetuadas entre a obtenção e a regravação do item de tabela.

    A segunda maneira de executar essa operação é usar a chamada da API UpdateItem no DynamoDB. Com a API UpdateItem, é possível atualizar o item do DynamoDB com uma única solicitação. Especifique o item que você deseja alterar, os atributos a serem alterados e as condições a serem expressas no item. Esse é o método preferencial de efetuar uma alteração em um item porque não requer várias chamadas ao banco de dados.

    No diretório scripts/, há um arquivo chamado performMove.js, que tem o seguinte conteúdo:

    const AWS = require('aws-sdk')
    const documentClient = new AWS.DynamoDB.DocumentClient()
    
    const performMove = async ({ gameId, user, changedHeap, changedHeapValue }) => {
      if (changedHeapValue < 0 ) {
        throw new Error('Cannot set heap value below 0')
      }
      const params = {
        TableName: 'turn-based-game',
        Key: { 
          gameId: gameId
        },
        UpdateExpression: `SET lastMoveBy = :user, ${changedHeap} = :changedHeapValue`,
        ConditionExpression: `(user1 = :user OR user2 = :user) AND lastMoveBy <> :user AND ${changedHeap} > :changedHeapValue`,
        ExpressionAttributeValues: {
          ':user': user,
          ':changedHeapValue': changedHeapValue,
        },
        ReturnValues: 'ALL_NEW'
      }
      try {
        const resp = await documentClient.update(params).promise()
        console.log('Updated game: ', resp.Attributes)
      } catch (error) {
        console.log('Error updating item: ', error.message)
      }
    }
    
    performMove({ gameId: '5b5ee7d8', user: 'theseconduser', changedHeap: 'heap1', changedHeapValue: 3 })

    Esse script é um pouco complicado. Vamos segui-lo passo a passo.

    Como no script anterior, importe o AWS SDK e crie uma instância do DynamoDB Document Client.

    Em seguida, defina um método chamado performMove. Esse método é semelhante a um método interno que seria usado no aplicativo quando o usuário solicitasse uma jogada. O script monta os parâmetros de uma chamada da API UpdateItem. Primeiramente, ele altera dois atributos do jogo: o último usuário a fazer uma jogada e o número de elementos na pilha alterada.

    Os parâmetros da API UpdateItem fazem algumas afirmativas sobre o estado atual do jogo. ConditionExpression é avaliado antes de o item ser atualizado para confirmar que o estado do item é o desejado. Você está fazendo estas três afirmativas na expressão da condição:

    1. O usuário que solicita realizar uma jogada é um dos dois usuários do jogo;
    2. A rodada atual é do usuário que está solicitando a jogada;
    3. O valor atual da pilha que está sendo alterada é superior ao valor para o qual está sendo alterado.

    Por último, os parâmetros definem ReturnValue de ALL_NEW, o que significa que o DynamoDB retorna o item inteiro depois que seus valores são atualizados. Depois disso, o aplicativo pode avaliar o jogo para ver se há um vencedor.

    Na parte inferior do arquivo, há um exemplo de como esse método é chamado no aplicativo. É possível executar o script com o seguinte comando:

    node scripts/performMove.js

    A seguinte saída será exibida no terminal:

    Updated game:  { heap2: 4,
      heap1: 3,
      heap3: 5,
      gameId: '5b5ee7d8',
      user2: 'theseconduser',
      user1: 'myfirstuser',
      lastMoveBy: 'theseconduser' }

    Você pode ver que a gravação foi bem-sucedida e que o jogo foi atualizado.

    Tente executar o script no terminal novamente. A seguinte saída será exibida:

    Error updating item:  The conditional request failed

    As solicitações condicionais falharam desta vez. O usuário solicitante -- theseconduser -- foi o jogador que realizou a jogada mais recente. Além disso, a pilha alterada -- heap1 -- já tinha o valor de 3, ou seja, o usuário não efetuou qualquer alteração. Por esse motivo, a solicitação foi recusada.


Neste módulo, você provisionou um banco de dados Amazon DynamoDB para armazenar dados do jogo. Você aprendeu sobre as chaves primárias do DynamoDB para a modelagem de dados. Após criar uma tabela, você aprendeu a inserir itens nela para armazenar o estado inicial do jogo. Por fim, você viu como atualizar itens nos dados em uma única solicitação, para evitar diversas solicitações ao DynamoDB.

No próximo módulo, você aprenderá a usar o Amazon Simple Notification Service (Amazon SNS) para enviar mensagens SMS para notificar os usuários sobre eventos importantes no jogo.