在本模块,您将预置 Amazon DynamoDB 数据库,了解如何使用 DynamoDB 存储回合制游戏的相关信息。

完成模块所需时间:30 分钟


Amazon DynamoDB 是由 AWS 提供的完全托管的 NoSQL 数据库服务。它提供个位数的毫秒响应时间和近乎无限的可扩展性。从 Amazon.com 购物车到 Lyft’s 地理定位服务,再到各种网络游戏,DynamoDB 广泛应用于各项应用程序和各行各业。

与 DynamoDB 的所有交互均通过 HTTPS 完成,使用 AWS Identity and Access Management (IAM) 进行身份验证和授权。通常情况下,您使用适用于您所选语言的 AWS 开发工具包与 DynamoDB 进行交互。如果您为您的应用程序使用 AWS 计算选项(例如 Amazon Elastic Compute Cloud (Amazon EC2) 或 AWS Lambda),则您的应用程序可以使用您的计算环境中的 AWS 凭证向 DynamoDB 发出请求。

在以下步骤中,您首先预置 DynamoDB 数据库。然后,您学习如何使用适用于 Node.js 中 JavaScript 的 AWS 开发工具包与您的 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。然后,它声明表的主键中使用的属性。在本示例中,您使用的是简单的主键,因此您只声明了一个属性,即 gameId,这是字符串数据类型。接下来,声明将 gameId 属性用作表的哈希键,从而指定主键 schema。

    最后,指定所需的表的读取和写入容量单位数。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 步:在表中保存游戏项目示例

    既然您已创建表,现在可以为表添加项目了。每个游戏都由表中的一个项目表示。

    每个游戏项目的 schema 如下:

    每个游戏都包含一个 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 开发工具包,然后创建 DynamoDB 文档客户端的实例。文档客户端是底层 DynamoDB API 之上的高级抽象,让 DynamoDB 项目处理变得更加简单。创建客户端之后,脚本会整合 PutItem API 调用的参数,包括表名称和项目属性。然后,它会在文档客户端上调用 put() 方法,并记录成功或失败的信息。

    您可以在终端中运行以下命令,从而运行脚本,将游戏插入表中:

    node scripts/createGame.js

    您应该能在终端中看到以下输出:

    Game added successfully!

    注意:如果您运行命令过快,可能会收到一个错误,即该表尚不可用。等待一分钟,然后重试该命令。

    很好! 您现在已为您的表添加一个游戏。在下一步中,您将学习如何更新该游戏项目,以模拟用户执行操作。

  • 第 3 步:更新表中的游戏项目

    既然您的表中已有游戏项目,现在可以学习如何模拟游戏玩家在进行中的游戏中执行操作。

    此操作有两种处理方式。在第一种方式中,您使用 GetItem API 检索项目。然后,根据游戏玩家的操作更新应用程序中的游戏项目。最后,使用 PutItem API 替换 DynamoDB 中的现有项目。虽然此方式奏效,但它需要向 DynamoDB 表发出多个请求,并且可能会覆盖在获取和重写游戏项目之间发生的任何更改。

    此操作的第二种处理方式是在 DynamoDB 中使用 UpdateItem API 调用。借助 UpdateItem API,您可以通过单一请求更新适当的 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 开发工具包并创建 DynamoDB 文档客户端实例。

    然后,定义名为 performMove 的方法。此方法类似于用户请求进行操作时在应用程序中使用的内部方法。该脚本会整合 UpdateItem API 调用的参数。首先,它会更改游戏中的两个属性,即执行操作的最后一名用户和更改堆中的元素数量。

    然后,UpdateItem API 参数会对游戏的当前状态进行一些断言。在更新项目之前,ConditionExpression 会被评估,以确认项目处于您希望的状态。您在条件表达式中做出以下三个断言:

    1. 请求执行某操作的用户是游戏中的两名用户之一;
    2. 当前回合属于请求执行该操作的用户;
    3. 所更改的堆的当前值高于更改目标值。

    最后,参数声明 ALL_NEW 的 ReturnValue ,也就是说,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 发出多个请求。

在下一个模块中,您将学习如何使用 Amazon Simple Notification Service (Amazon SNS) 发送短信,以告知用户游戏中的重要事件。