このモジュールでは、Amazon DynamoDB データベースをプロビジョニングし、DynamoDB を使用してターン制ゲームに関する情報を保存する方法を学習します。

モジュールの所要時間: 30 分


Amazon DynamoDB は、AWS が提供する完全マネージド型の NoSQL データベースです。1 桁のミリ秒の応答時間とほぼ無限のスケーラビリティを提供します。DynamoDB は、Amazon.com ショッピングカートから Lyft's 位置情報サービス、さまざまなオンラインゲームまで、さまざまなアプリケーションや業界で使用されています。

DynamoDB とのすべてのやり取りは、認証と承認のために AWS Identity and Access Management (IAM) を使用して HTTPS 経由で行われます。通常、選択した言語の AWS SDK を使用して DynamoDB とやり取りします。Amazon Elastic Compute Cloud (Amazon EC2) または AWS Lambda など、アプリケーションに AWS コンピューティングオプションを使用している場合、アプリケーションはコンピューティング環境で AWS 認証情報を使用して DynamoDB にリクエストを送信できます。

次の手順では、最初に DynamoDB データベースをプロビジョニングします。次に、Node.js 内の AWS SDK for JavaScript を使用して、DynamoDB データベースとやり取りする方法を学びます。


  • ステップ 1.Amazon DynamoDB データベースをプロビジョニングする

    最初に、DynamoDB データベースをプロビジョニングしましょう。DynamoDB のデータベースは、テーブルとも呼ばれます。

    DynamoDB テーブルを作成する場合、テーブルのプライマリキーを構成する属性を指定する必要があります。DynamoDB テーブルに書き込む各レコードはアイテムと呼ばれ、各アイテムにはテーブルのプライマリキーを含める必要があります。

    DynamoDB データモデリングとプライマリキーの設計は重要なトピックです。ただし、ゲームにはシンプルなアクセスパターンがあり、高度なデータモデリングを必要としないため、このチュートリアルでは DynamoDB データモデリングの高度なトピックについては扱いません。DynamoDB でのデータのモデリングの詳細については、「Amazon DynamoDB でモバイルアプリ用のデータベースを設計する」や「Amazon DynamoDB を使用したゲームアプリケーションのデータモデリング」に関するチュートリアルを参照してください。

    アプリケーションでは、DynamoDB を単純なキーと値のストアとして使用します。各ゲームには、各ゲームを一意に識別する gameId 属性があります。gameId 属性は、テーブルのプライマリキーとして使用します。

    scripts/ ディレクトリには、create-table.sh というファイルがあります。このファイルは、AWS コマンドラインインターフェイス (AWS CLI) を使用して DynamoDB テーブルを作成します。ファイルの内容は次のとおりです。
     

    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 という名前を付けます。次に、テーブルのプライマリキーで使用する属性を宣言します。この例では単純なプライマリキーを使用しているため、文字列データ型の 1 つの属性である 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 が含まれます。属性 User1User2 は、ゲームをプレイしている 2 人のユーザーのユーザー名を格納します。属性 Heap1Heap2、および Heap3 は、3 つの各ヒープ内のオブジェクトの数を格納します。最後に、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 Document Client のインスタンスを作成します。Document Client は、低レベルの DynamoDB API に対する高レベルの抽象化であり、DynamoDB アイテムの操作を容易にします。クライアントを作成した後、スクリプトは、テーブル名やアイテムの属性など、PutItem API 呼び出しのパラメータを組み立てます。次に、Document Client で put() メソッドを呼び出し、成功または失敗時に情報をログアウトします。

    ターミナルで次のコマンドを実行することで、スクリプトを実行してゲームをテーブルに挿入できます。

    node scripts/createGame.js

    ターミナルに次の出力が表示されます。

    Game added successfully!

    注: コマンドの実行が速すぎると、テーブルがまだ利用できないというエラーが表示される場合があります。1 分待ってから、コマンドを再試行してみてください。

    素晴らしい! これで、1 つのゲームがテーブルに追加されました。次のステップでは、そのゲームアイテムを更新して、ユーザーの行動をシミュレートする方法を学習します。

  • ステップ 3.テーブル内のゲームアイテムを更新する

    テーブルにゲームアイテムができたので、進行中のゲームで行動しているプレイヤーをシミュレートする方法を学習できます。

    この操作を処理できる方法は 2 つあります。最初の方法では、GetItem API を使用してアイテムを取得します。次に、プレイヤーが実行した行動に応じて、アプリケーションのゲームアイテムを更新します。最後に、PutItem API を使用して DynamoDB の既存のアイテムを置き換えます。このオプションは機能しますが、DynamoDB テーブルへの複数のリクエストが必要で、ゲームアイテムの取得と再書き込みの間に発生した変更を上書きしてしまうリスクがあります。

    この操作を処理する 2 番目の方法は、DynamoDB で UpdateItem API 呼び出しを使用することです。UpdateItem API を使用すると、1 回のリクエストで所定の場所にある 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 Document Client インスタンスを作成します。

    次に、performMove というメソッドを定義します。このメソッドは、ユーザーが行動を行うことをリクエストしたときにアプリケーションで使用する内部メソッドに似ています。このスクリプトは、UpdateItem API 呼び出しのパラメータを組み立てます。まず、ゲームの 2 つの属性を変更します。その 2 つは、最後に移動したユーザーと、変更されたヒープ内の要素の数です。

    UpdateItem API パラメータは、ゲームの現在の状態についていくつかのアサーションを作成します。ConditionExpression は、アイテムが更新される前に評価され、アイテムが目的の状態にあることを確認します。条件式で次の 3 つのアサーションを作成しています。

    1. 行動の実行をリクエストしているユーザーがゲーム内の 2 人のユーザーのうちの 1 人であること。
    2. 現在の順番は、行動の実行をリクエストしているユーザーのものであること。
    3. 変更されるヒープの現在の値が、変更される値よりも高いこと。

    最後に、パラメータは ALL_NEWReturnValue を示します。これは、値が更新された後に 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 のプライマリキーについて学習しました。テーブルを作成した後、ゲームの初期状態を保存するためにテーブルにアイテムを挿入する方法を学びました。最後に、1 つのリクエストで DynamoDB に複数のリクエストを行うことを避けるために、データ内のアイテムを更新する方法を見ました。

次のモジュールでは、Amazon Simple Notification Service (Amazon SNS) を使用して SMS メッセージを送信し、ゲームの重要なイベントについてユーザーに通知する方法について学習します。