构建新应用程序时,在开始实施之前计划数据模型非常重要。这种方法可确保为您提供一个坚实的基础以构建您的应用程序。在本模块中,您将了解应用程序中的主要实体,计划数据模型并准备数据库。

计划数据模型的一种常见方法是创建一个实体关系图 (ERD)。ERD 显示应用程序中的不同实体及其属性。它还显示了实体之间的相互关系。

在您的简单应用程序中,您只有一个实体 Games。一个 Game 表示用户某次在游戏中玩过的特定关卡。它存储用户的用户名、游戏关卡、游戏的时间戳和用户在游戏中的得分。

应用程序的 ERD 很简单,因为只有一个实体,且没有相互关系。如下所示。

leaderboard-erd

您可以看到 ERD 包含单个实体 Game 以及该实体的各种属性。

在以下步骤中,您将 ERD 转换为数据库代码。首先,创建与 ERD 匹配的表。然后,将一些示例数据加载到数据库中。最后,您在数据库上运行一些查询以处理一些用例。

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


  • 第 1 步:创建数据库表

    首先,创建数据库表。在 scripts/ 目录中,有一个名为 createTables.js 的文件。该文件的内容如下:

    const AWS = require('aws-sdk')
    
    const rdsdataservice = new AWS.RDSDataService();
    
    const params = {
      resourceArn: process.env.DATABASE_ARN,
      secretArn: process.env.SECRET_ARN,
      database: 'leaderboard',
      sql: `CREATE TABLE games (
    game_id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    gamedate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    score INT NOT NULL,
    level INT NOT NULL
    );`
    }
    
    rdsdataservice.executeStatement(params, function(err, data) {
      if (err) {
        console.log(err, err.stack)
      } else {
        console.log('Table created successfully!')
      }
    })

    此脚本类似于您在上一个模块中运行的 testDatabase.js 脚本。在此脚本中,SQL 包含用于创建 Games 表的数据定义语言 (DDL) 语句。该表具有一个 game_id 属性,该属性是一个自动递增的整数,用作该表的主键。它还具有其他属性,包括用户名和分数。

    执行脚本并使用以下命令创建表:

    node scripts/createTables.js

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

    Table created successfully!
  • 第 2 步:加载样本数据

    现在您已经创建了表,可加载一些样本数据。

    scripts/ 目录中,有一个包含一些 JSON 数据的 games.json 文件。此 JSON 数据是要插入到表中的随机示例数据。

    查看 scripts/insertGames.js 中的文件。它包含以下代码:

    const AWS = require('aws-sdk')
    
    const rdsdataservice = new AWS.RDSDataService();
    const fs = require('fs');
    const path = require('path');
    
    const raw = fs.readFileSync(path.resolve( __dirname, 'games.json'));
    const games = JSON.parse(raw)
    const values = games.map((game) => { return `('${game.username}', '${game.gamedate}', ${game.score}, ${game.level})`}).join(',\n')
    const sql = `INSERT INTO games (username, gamedate, score, level) VALUES ${values}`
    
    const params = {
      resourceArn: process.env.DATABASE_ARN,
      secretArn: process.env.SECRET_ARN,
      database: 'leaderboard',
      sql
    }
    
    rdsdataservice.executeStatement(params, function(err, data) {
      if (err) {
        console.log(err, err.stack)
      } else {
        console.log('Games inserted successfully!')
      }
    })

    createTables.js 脚本一样,它使用 RDSDataService 客户端访问 Data API。在此脚本中,您正在从 games.json 文件中读取假冒游戏,然后在 SQL 中编写 INSERT 语句,以将游戏数据插入表中。

    您可以使用以下命令运行脚本: 

    node scripts/insertGames.js

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

    Games inserted successfully!

    您现在已将 300 个不同的游戏分数加载到您的表中。在下一步,您将了解如何使用 Data API 处理其中一种常见访问模式。

  • 第 3 步:测试数据访问

    加载数据后,您可以使用 Data API 执行比上一个模块中执行的 Select 1 查询更复杂的操作。

    常见的访问模式是为用户获取最高分数。您可以在此处尝试。

    查看 scripts/fetchHighScoresForUser.js 中的代码。此代码包含应用程序调用以获取特定用户最高分数的内部方法。代码如下所示:

    const AWS = require('aws-sdk')
    
    const rdsdataservice = new AWS.RDSDataService();
    
    const fetchHighScoresForUser= async (username, count) => {
      const params = {
        resourceArn: process.env.DATABASE_ARN,
        secretArn: process.env.SECRET_ARN,
        database: 'leaderboard',
        includeResultMetadata: true,
        sql: 'SELECT game_id, username, gamedate, score, level FROM games WHERE username = :username ORDER BY score DESC LIMIT :count',
        parameters: [
          {
            name: 'username',
            value: { stringValue: username }
          },
          {
            name: 'count',
            value: { longValue: count }
          }
        ]
      }
      const results = await rdsdataservice.executeStatement(params).promise()
      return results
    }
    
    fetchHighScoresForUser(ubecker, 1).then((results) => console.log(JSON.stringify(results, null, 2)))

    您的 fetchHighScoresForUser 函数采用两个参数:要为之获取高分的用户名和要获取的记录数。然后,它使用数据 API 进行查询,以获取该用户的最高分数。

    文件底部是一个使用 fetchHighScoresForUser 函数的示例,方法是使用用户名 ubecker 调用该函数,并仅请求一条记录。

    通过在终端中运行以下命令来执行此脚本:

    node scripts/fetchHighScoresForUser.js

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

    {
      "columnMetadata": [
        {
          "arrayBaseColumnType": 0,
          "isAutoIncrement": true,
          "isCaseSensitive": false,
          "isCurrency": false,
          "isSigned": true,
          "label": "game_id",
          "name": "game_id",
          "nullable": 0,
          "precision": 11,
          "scale": 0,
          "schemaName": "",
          "tableName": "games",
          "type": 4,
          "typeName": "INT"
        },
        {
          "arrayBaseColumnType": 0,
          "isAutoIncrement": false,
          "isCaseSensitive": false,
          "isCurrency": false,
          "isSigned": false,
          "label": "username",
          "name": "username",
          "nullable": 0,
          "precision": 50,
          "scale": 0,
          "schemaName": "",
          "tableName": "games",
          "type": 12,
          "typeName": "VARCHAR"
        },
        {
          "arrayBaseColumnType": 0,
          "isAutoIncrement": false,
          "isCaseSensitive": false,
          "isCurrency": false,
          "isSigned": false,
          "label": "gamedate",
          "name": "gamedate",
          "nullable": 0,
          "precision": 19,
          "scale": 0,
          "schemaName": "",
          "tableName": "games",
          "type": 93,
          "typeName": "TIMESTAMP"
        },
        {
          "arrayBaseColumnType": 0,
          "isAutoIncrement": false,
          "isCaseSensitive": false,
          "isCurrency": false,
          "isSigned": true,
          "label": "score",
          "name": "score",
          "nullable": 0,
          "precision": 11,
          "scale": 0,
          "schemaName": "",
          "tableName": "games",
          "type": 4,
          "typeName": "INT"
        },
        {
          "arrayBaseColumnType": 0,
          "isAutoIncrement": false,
          "isCaseSensitive": false,
          "isCurrency": false,
          "isSigned": true,
          "label": "level",
          "name": "level",
          "nullable": 0,
          "precision": 11,
          "scale": 0,
          "schemaName": "",
          "tableName": "games",
          "type": 4,
          "typeName": "INT"
        }
      ],
      "numberOfRecordsUpdated": 0,
      "records": [
        [
          {
            "longValue": 101
          },
          {
            "stringValue": "ubecker"
          },
          {
            "stringValue": "2019-11-06 09:00:37"
          },
          {
            "longValue": 9090
          },
          {
            "longValue": 84
          }
        ]
      ]
    }

    此输出非常详细。Data API 包含有关结果的大量信息,包括返回的每列的详细列元数据。

    在每个数据访问方法中,此信息可能难以解析。在下一步中,您将使用实用方法来打包 Data API。

  • 第 4 步:解析 Data API 响应

    在上一步中,您了解了获取单个用户高分的示例方法,以及来自 Data API 的详细响应。在此步骤中,您将学习如何解析该响应。

    scripts/ 目录中,查看 fetchHighScoresForUser2.js 文件。此文件的内容如下:

    const AWS = require('aws-sdk')
    
    const rdsdataservice = new AWS.RDSDataService();
    
    const parseRecords = (records, columnMetadata) => {
      // Format the results into key-value pairs with the column name and value
      const parsed = records.map((result) => {
        const obj = {}
        result.forEach((elem, idx) => {
          const columnName = columnMetadata[idx].name
          const [ columnValue, ]= Object.values(elem)
          obj[columnName] = columnValue
        })
        return obj
      })
      return parsed
    
    }
    
    const executeReadSql = async (sql, parameters) => {
      const params = {
        resourceArn: process.env.DATABASE_ARN,
        secretArn: process.env.SECRET_ARN,
        database: 'leaderboard',
        includeResultMetadata: true,
        sql
      }
      if (parameters) {
        params.parameters = parameters
      }
      const rawResults = await rdsdataservice.executeStatement(params).promise()
      let results = []
      if (rawResults.records) {
        results = parseRecords(rawResults.records, rawResults.columnMetadata)
      }
      return results
    }
    
    const fetchHighScoresForUser = async (username, count) => {
      const parameters = [
        {
          name: 'username',
          value: { stringValue: username }
        },
        {
          name: 'count',
          value: { longValue: count }
        }
      ]
      const sql = 'SELECT game_id, username, gamedate, score, level FROM games WHERE username = :username ORDER BY score DESC LIMIT :count'
      const result = await executeReadSql(sql, parameters)
      return result
    }
    
    fetchHighScoresForUser(ubecker, 1).then((results) => console.log(JSON.stringify(results, null, 2)))

    在此脚本中,有两个帮助程序函数:executeReadSqlparseRecordsexecuteReadSql 函数简化了调用 Data API 的一些样板文件。它处理外部参数,如 Database ARN 和 Secret ARN,以便您只能专注于数据访问函数中的 SQL 和参数。

    parseRecords 函数帮助将返回的行更改为具有键值对的 JavaScript 对象。这种结果在您的应用程序中更容易处理。

    尝试运行脚本以获取单个用户。在终端中执行以下命令:

    node scripts/fetchHighScoresForUser2.js

    您应该能看到以下输出:

    [
      {
        "game_id": 101,
        "username": "ubecker",
        "gamedate": "2019-11-06 09:00:37",
        "score": 9090,
        "level": 84
      }
    ]
    

    与初始结果相比,此输出更易于在应用程序中读取和使用。

    您可以在应用程序中的数据访问功能中使用这些实用功能。


    在本模块中,您使用实体关系图 (ERD) 设计了数据模型。然后,您使用 Amazon Aurora Serverless 数据库中的 Data API 创建表。接下来,您将一些示例数据加载到新表中。最后,您还学习了一些使用 Data API 的示例。您已经了解了一些实用函数在使用 Data API 时有何帮助。

    在下一个模块中,您将创建 ElastiCache 实例并对其进行配置,以便可以从 Cloud9 实例访问该实例。