В этом модуле вы узнаете, как предоставить базу данных Amazon DynamoDB и хранить в ней информацию о пошаговой игре.

Время, необходимое для прохождения модуля: 30 минут


Amazon DynamoDB – полностью управляемая база данных NoSQL на платформе AWS. Она обеспечивает низкую задержку менее 10 миллисекунд и практически неограниченную масштабируемость. DynamoDB используется во множестве приложений в разных отраслях: от корзины для интернет‑покупок на сайте Amazon.com и геолокационной службы Lyft до широкого спектра онлайн-игр.

Взаимодействие с DynamoDB осуществляется по протоколу HTTPS с использованием сервиса AWS Identity and Access Management (IAM) для аутентификации и авторизации. Как правило, для работы с DynamoDB выбирают пакет AWS SDK для нужного языка. Если для приложения используются вычислительные возможности AWS, например Amazon Elastic Compute Cloud (Amazon EC2) или AWS Lambda, оно может отправлять запросы к DynamoDB с применением учетных данных AWS в вычислительной среде.

Следуя приведенным здесь инструкциям, сначала вы предоставите базу данных DynamoDB, а потом научитесь взаимодействовать с ней с помощью AWS SDK для JavaScript в Node.js.


  • Шаг 1. Предоставление базы данных Amazon DynamoDB

    Сначала предоставим базу данных DynamoDB. Она также называется таблицей.

    При создании таблицы DynamoDB необходимо указать атрибуты, на основе которых будет создан ее первичный ключ. Каждая добавляемая в таблицу DynamoDB запись называется элементом, каждый из которых должен содержать первичный ключ таблицы.

    Моделирование данных и схема первичного ключа DynamoDB – важная тема. Однако создаваемая в рамках этого учебника игра имеет простую схему доступа, не требующую передовых методов моделирования данных DynamoDB, поэтому они здесь не рассматриваются. Дополнительные сведения о моделировании данных в DynamoDB можно найти в учебниках по разработке базы данных для мобильного приложения с помощью Amazon DynamoDB или моделировании данных для игрового приложения в Amazon DynamoDB.

    В вашем приложении DynamoDB используется в качестве простого хранилища типа «ключ – значение». У каждой игры есть атрибут gameId, который ее однозначно идентифицирует. Атрибут gameId выступает первичным ключом для таблицы.

    В каталоге scripts/ размещен файл create-table.sh, который позволяет создать таблицу DynamoDB с помощью интерфейса командной строки AWS (AWS CLI). Этот файл содержит следующий код:
     

    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
      }'

    Сначала таблице присваивается имя turn-based-game. Затем объявляются атрибуты, используемые в первичном ключе таблицы. В этом примере используется простой первичный ключ, поэтому нужно объявить только один атрибут – gameId, который относится к строковому типу данных. Затем в коде задается схема первичного ключа: атрибут gameId указывается как хэш-ключ таблицы.

    Наконец, в коде задается требуемое количество единиц ресурсов записи и чтения для таблицы. DynamoDB поддерживает модель оплаты по требованию, когда нужно платить за каждый запрос на чтение и запись к таблице. Однако на уровне бесплатного пользования AWS доступен режим выделенной пропускной способности, который можно использовать в нашем случае.

    Создайте таблицу, выполнив следующею команду в терминале:

    bash scripts/create-table.sh

    В терминале отобразятся такие выходные данные:

    {
        "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"
        }
    }
  • Шаг 2. Сохранение примера элемента игры в таблице

    Теперь, когда таблица создана, в нее можно добавить элементы. Каждая игра представлена одним элементом в таблице.

    Каждый элемент игры имеет следующую схему:

    Каждая игра содержит атрибут gameId, который является ее уникальным идентификатором. В атрибутах User1 и User2 хранятся имена двух игроков этой игры. В атрибутах Heap1, Heap2 и Heap3 хранится число объектов в каждой из трех куч. Последний атрибут, LastMoveBy, указывает игрока, совершившего последнее действие.

    В каталоге scripts/ размещен файл createGame.js, который добавляет пример игры в таблицу. Этот файл содержит следующий код:

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

    Этот скрипт импортирует пакет AWS SDK, а затем создает инстанс клиента документов DynamoDB. Клиент документов – это абстракция верхнего уровня поверх DynamoDB API нижнего уровня, которая упрощает работу с элементами DynamoDB. После создания клиента скрипт собирает параметры для вызова API PutItem, в том числе имя таблицы и атрибуты элемента. Затем он вызывает метод put() для клиента документов и выводит информацию об удачном или неудачном результате.

    Выполните скрипт, чтобы вставить игру в таблицу, запустив следующую команду в терминале:

    node scripts/createGame.js

    В терминале отобразятся такие выходные данные:

    Game added successfully!

    Примечание. Если вы запустите команду очень быстро, она может вернуть ошибку о том, что таблица еще недоступна. Подождите одну минуту, а затем попробуйте выполнить команду снова.

    Отлично! Теперь в таблицу добавлена игра. Далее вы узнаете, как обновить элемент игры для моделирования действия, выполняемого пользователем.

  • Шаг 3. Обновление элемента игры в таблице

    Теперь, когда в таблице есть элемент игры, можно научиться моделировать действие, выполняемое игроком в ходе игры.

    Это можно сделать двумя способами. В рамках первого способа нужно получить элемент, используя API GetItem. Затем необходимо обновить элемент игры в приложении в соответствии с выполненным игроком действием. Наконец, следует заменить существующий элемент в DynamoDB с помощью API PutItem. Хотя этот способ работает, в нем требуется множество запросов к таблице DynamoDB и есть риск перезаписи изменений, произошедших в период между получением и перезаписыванием элемента игры.

    Второй способ – использовать вызов API UpdateItem в DynamoDB. API UpdateItem позволяет обновить элемент DynamoDB с помощью одного запроса. Вам нужно указать элемент и атрибуты для изменения, а также условия, которые требуется объявить для элемента. Это предпочтительный способ внесения изменений в элемент, поскольку не требуется отправлять множество вызовов к базе данных.

    В каталоге scripts/ размещен файл performMove.js со следующим кодом:

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

    Это немного сложный скрипт. Рассмотрим его поэтапно.

    Как и в предыдущем скрипте, вы импортируете пакет AWS SDK и создаете инстанс клиента документов DynamoDB.

    Затем нужно определить метод performMove. Он схож с внутренним методом, используемым в приложении, когда пользователь запрашивает выполнение действия. Этот скрипт собирает параметры для вызова API UpdateItem. Сначала он изменяет два атрибута игры – последнего пользователя, выполнившего действие, и количество элементов в измененной куче.

    Затем параметры API UpdateItem делают некоторые утверждения о текущем состоянии игры. До обновления элемента вычисляется выражение ConditionExpression, чтобы подтвердить, что элемент находится в нужном состоянии. В выражении условия нужно сделать следующие три утверждения.

    1. Что пользователь, запрашивающий выполнение действия, является одним из двух игроков.
    2. Что сейчас очередь пользователя, запрашивающего выполнение действия.
    3. Что текущее значение изменяемой кучи выше значения, которое для нее будет установлено.

    Наконец, в параметрах указан атрибут ReturnValue со значением ALL_NEW. Это означает, что DynamoDB возвращает весь элемент после обновления его значений. После этого приложение может оценить игру, чтобы определить, есть ли победитель.

    В конце файла приведен пример вызова этого метода в приложении. Выполните скрипт с помощью следующей команды:

    node scripts/performMove.js

    В терминале отобразятся такие выходные данные:

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

    Как видно, запись осуществлена и игра обновлена.

    Попробуйте снова запустить скрипт в терминале. В результате вы получите:

    Error updating item:  The conditional request failed

    На этот раз запросы с условием завершились сбоем. Запрашивающий пользователь, theseconduser, был последним игроком, выполнившим действие. У измененной кучи, heap1, уже было значение 3, а значит пользователь ничего не изменил. Из-за этого запрос был отклонен.


В этом модуле вы предоставили базу данных Amazon DynamoDB для хранения данных об игре, а также узнали о первичных ключах в DynamoDB и их роли в моделировании данных. Вы также научились вставлять элементы в созданную таблицу для хранения начального состояния игры. Наконец, вы узнали, как обновлять элементы в данных, чтобы избежать выполнения нескольких запросов к DynamoDB в рамках одного запроса.

В следующем модуле вы научитесь отправлять SMS-сообщения, извещающие пользователей о важных событиях их игры, с помощью сервиса Amazon Simple Notification Service (Amazon SNS).