在本模块,您将预置 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,这是游戏的唯一标识符。属性 User1User2 存储正在玩游戏的两名用户的用户名。属性 Heap1Heap2Heap3 存储三个堆各自的对象数。最后,属性 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_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 中的主键。在创建表之后,您学习了如何将项目插入表中以存储初始游戏状态。最后,您了解了如何更新数据中的项目,以避免在单个请求中向 DynamoDB 发出多个请求。

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