Trong mô-đun này, bạn sẽ cung cấp cơ sở dữ liệu Amazon DynamoDB và tìm hiểu cách sử dụng DynamoDB để lưu trữ thông tin về trò chơi theo lượt của mình.

Thời gian hoàn thành mô-đun: 30 phút


Amazon DynamoDB là cơ sở dữ liệu NoSQL được quản lý đầy đủ do AWS cung cấp. Cơ sở dữ liệu này cung cấp thời gian phản hồi trong vài mili giây và khả năng mở rộng gần như vô hạn. DynamoDB được sử dụng trong hàng loạt ứng dụng và ngành, từ giỏ hàng của Amazon.com cho đến dịch vụ định vị địa lý của Lyft cùng nhiều trò chơi trực tuyến đa dạng.

Mọi tương tác với DynamoDB được thực hiện qua HTTPS bằng AWS Identity and Access Management (IAM) để xác thực và ủy quyền. Bạn thường sử dụng AWS SDK cho ngôn ngữ bạn chọn để tương tác với DynamoDB. Nếu bạn đang sử dụng các tùy chọn điện toán AWS cho ứng dụng của mình, chẳng hạn như Amazon Elastic Compute Cloud (Amazon EC2) hoặc AWS Lambda thì ứng dụng của bạn có thể sử dụng thông tin xác thực AWS trong môi trường điện toán để gửi yêu cầu đến DynamoDB.

Trong các bước sau, trước tiên, bạn sẽ cung cấp cơ sở dữ liệu DynamoDB. Sau đó, bạn tìm hiểu cách tương tác với cơ sở dữ liệu DynamoDB của mình bằng AWS SDK dành cho JavaScript trong Node.js.


  • Bước 1. Cung cấp cơ sở dữ liệu Amazon DynamoDB

    Trước tiên, hãy cung cấp một cơ sở dữ liệu DynamoDB. Cơ sở dữ liệu trong DynamoDB còn được gọi là bảng.

    Khi tạo bảng DynamoDB, bạn cần chỉ định (các) thuộc tính sẽ tạo nên khóa chính trong bảng của mình. Mỗi bản ghi mà bạn ghi vào bảng DynamoDB được gọi là một mục và mỗi mục phải bao gồm khóa chính của bảng.

    Việc lập mô hình dữ liệu và thiết kế khóa chính của DynamoDB là một chủ đề quan trọng. Tuy nhiên, trò chơi của bạn có mẫu hình truy cập đơn giản, không yêu cầu lập mô hình dữ liệu nâng cao, do đó hướng dẫn này sẽ không bao hàm các chủ đề nâng cao về lập mô hình dữ liệu của DynamoDB. Để biết thêm chi tiết về việc lập mô hình dữ liệu trong DynamoDB, bạn có thể đọc hướng dẫn về Thiết kế cơ sở dữ liệu cho ứng dụng di động với Amazon DynamoDB hoặc Lập mô hình dữ liệu ứng dụng trò chơi với Amazon DynamoDB.

    Trong ứng dụng của bạn, bạn sử dụng DynamoDB như một kho lưu trữ khóa - giá trị đơn giản. Mỗi trò chơi có một thuộc tính gameId định danh duy nhất cho từng trò chơi. Thuộc tính gameId được dùng làm khóa chính cho bảng của bạn.

    Trong thư mục scripts/, có một tệp mang tên create-table.sh sẽ tạo bảng DynamoDB của bạn bằng AWS Command Line Interface (AWS CLI). Nội dung của tệp này như sau:
     

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

    Trước tiên, tệp đặt tên cho bảng là turn-based-game. Sau đó, tệp khai báo các thuộc tính dùng trong khóa chính của bảng. Bạn đang sử dụng một khóa chính đơn giản trong ví dụ này nên bạn chỉ cần khai báo một thuộc tính duy nhất, gameId, thuộc loại dữ liệu chuỗi. Tiếp theo, bạn chỉ định lược đồ khóa chính của mình bằng cách chỉ ra rằng thuộc tính gameId được dùng làm khóa băm cho bảng của bạn.

    Cuối cùng, bạn chỉ định số lượng đơn vị dung lượng đọc và ghi mong muốn cho bảng của mình. DynamoDB có chế độ giá cả theo nhu cầu, trong đó bạn trả tiền cho mỗi yêu cầu đọc và ghi đối với bảng của mình. Tuy nhiên, chế độ thông lượng được cung cấp phù hợp với Bậc miễn phí của AWS nên bạn có thể sử dụng chế độ đó ở đây.

    Tạo bảng bằng cách chạy lệnh sau trong terminal của bạn:

    bash scripts/create-table.sh

    Bạn sẽ thấy kết quả đầu ra như sau trong terminal của mình:

    {
        "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"
        }
    }
  • Bước 2. Lưu mục trò chơi ví dụ trong bảng của bạn

    Sau khi đã tạo bảng, giờ bạn có thể thêm các mục vào bảng của mình. Mỗi trò chơi được đại diện bởi một mục duy nhất trong bảng.

    Lược đồ của từng mục trò chơi như sau:

    Mỗi trò chơi bao gồm một GameId là mã định danh duy nhất của trò chơi. Các thuộc tính User1User2 lưu trữ tên người dùng của hai người dùng đang chơi trò chơi. Các thuộc tính Heap1, Heap2Heap3 lưu trữ số lượng đối tượng của từng đống trong số ba đống này. Cuối cùng, thuộc tính LastMoveBy cho biết người chơi nào đã thực hiện bước đi gần nhất.

    Trong thư mục scripts/ có tệp createGame.js, tệp này thêm trò chơi ví dụ vào bảng của bạn. Nội dung của tệp này như sau:

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

    Bạn nhập AWS SDK rồi tạo một phiên bản Máy khách tài liệu DynamoDB. Máy khách tài liệu là mức độ trừu tượng cao hơn API DynamoDB cấp thấp và giúp cho việc thao tác với các mục DynamoDB trở nên dễ dàng hơn. Sau khi tạo máy khách, tập lệnh sẽ tập hợp các tham số cho lệnh gọi API PutItem , bao gồm tên bảng và các thuộc tính về mục. Sau đó, tập lệnh sẽ gọi phương pháp put() trên Máy khách tài liệu và ghi nhật ký thông tin là thành công hay không.

    Bạn có thể chạy tập lệnh để chèn trò chơi vào bảng của mình bằng cách chạy lệnh sau trong terminal của bạn:

    node scripts/createGame.js

    Bạn sẽ thấy kết quả đầu ra như sau trong terminal của mình:

    Game added successfully!

    Lưu ý: Nếu chạy lệnh quá nhanh, bạn có thể gặp lỗi bảng chưa khả dụng. Hãy đợi một phút, sau đó thử lại lệnh.

    Tuyệt vời! Hiện bạn đã thêm một trò chơi vào bảng của mình. Trong bước tiếp theo, bạn sẽ tìm hiểu cách cập nhật mục trò chơi đó để mô phỏng nước đi của người dùng.

  • Bước 3. Cập nhật mục trò chơi trong bảng của bạn

    Giờ đây, khi đã có một mục trò chơi trong bảng, bạn có thể tìm hiểu cách mô phỏng nước đi của người chơi cho một trò chơi đang diễn ra.

    Bạn có thể xử lý thao tác này theo hai cách. Cách thứ nhất là bạn truy xuất mục bằng API GetItem. Sau đó, bạn cập nhật mục trò chơi trong ứng dụng của mình theo nước đi của người chơi. Cuối cùng, bạn thay thế mục hiện có trong DynamoDB bằng API PutItem. Tùy chọn này hiệu quả nhưng đòi hỏi nhiều yêu cầu đối với bảng DynamoDB của bạn và có nguy cơ ghi đè mọi thay đổi xảy ra trong quá trình tải và ghi lại mục trò chơi.

    Cách thứ hai để xử lý thao tác này là sử dụng lệnh gọi API UpdateItem trong DynamoDB. Với API UpdateItem, bạn có thể cập nhật mục DynamoDB của mình tại chỗ thông qua một yêu cầu. Bạn chỉ định mục mình muốn thay đổi, các thuộc tính cần thay đổi và mọi điều kiện bạn muốn xác nhận trên mục đó. Đây là cách nên dùng để thực hiện thay đổi đối với mục vì cách này không yêu cầu nhiều lệnh gọi đến cơ sở dữ liệu của bạn.

    Trong thư mục scripts/, có một tệp mang tên performMove.js chứa nội dung như sau:

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

    Tập lệnh này hơi phức tạp nên hãy tiến hành từng bước một.

    Giống như tập lệnh trước, bạn nhập AWS SDK và tạo phiên bản Máy khách tài liệu DynamoDB.

    Sau đó, bạn xác định một phương pháp có tên performMove. Phương pháp này tương tự như một phương pháp nội bộ sẽ được dùng trong ứng dụng của bạn khi người dùng yêu cầu thực hiện nước đi. Tập lệnh sẽ tập hợp các tham số cho lệnh gọi API UpdateItem. Trước tiên, tập lệnh thay đổi hai thuộc tính trong trò chơi -- người dùng cuối cùng thực hiện nước đi và số lượng thành phần trong đống đã thay đổi.

    Sau đó, các tham số API UpdateItem đưa ra một số xác nhận về trạng thái hiện tại của trò chơi. ConditionExpression được đánh giá trước khi cập nhật mục để xác nhận rằng mục đó đang ở trạng thái bạn muốn. Bạn đang xác nhận ba điều sau trong biểu thức điều kiện của mình:

    1. Người dùng yêu cầu thực hiện nước đi là một trong hai người dùng trong trò chơi;
    2. Lượt hiện tại thuộc về người dùng yêu cầu thực hiện nước đi;
    3. Giá trị hiện tại của đống được thay đổi cao hơn giá trị sau khi thay đổi.

    Cuối cùng, các tham số xác định ReturnValue của ALL_NEW, nghĩa là DynamoDB trả về toàn bộ mục sau khi cập nhật giá trị. Sau khi có thông tin này, ứng dụng của bạn có thể đánh giá trò chơi để xem có người thắng hay không.

    Ở cuối tệp có một ví dụ về cách gọi phương pháp này trong ứng dụng của bạn. Bạn có thể thực thi tập lệnh bằng lệnh sau:

    node scripts/performMove.js

    Bạn sẽ thấy kết quả đầu ra như sau trong terminal của mình:

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

    Bạn có thể thấy tác vụ ghi đã thành công và trò chơi đã được cập nhật.

    Hãy thử chạy lại tập lệnh trong terminal của bạn. Bạn sẽ thấy kết quả đầu ra như sau:

    Error updating item:  The conditional request failed

    Yêu cầu có điều kiện của bạn lần này không thành công. Người dùng yêu cầu -- theseconduser -- là người chơi đã thực hiện nước đi gần nhất. Ngoài ra, đống được thay đổi -- heap1 -- đã có giá trị là 3, nghĩa là người dùng không hề thay đổi gì. Do đó, yêu cầu đã bị từ chối.


Trong mô-đun này, bạn đã cung cấp cơ sở dữ liệu Amazon DynamoDB để lưu trữ dữ liệu trò chơi. Bạn đã tìm hiểu các khóa chính trong DynamoDB ở bước lập mô hình dữ liệu. Sau khi tạo bảng, bạn đã học cách chèn mục vào bảng để lưu trữ trạng thái trò chơi ban đầu. Cuối cùng, bạn đã quan sát cách cập nhật mục trong dữ liệu để tránh gửi nhiều yêu cầu đến DynamoDB trong một yêu cầu.

Trong mô-đun tiếp theo, bạn sẽ tìm hiểu cách sử dụng Amazon Simple Notification Service (Amazon SNS) để gửi tin nhắn SMS nhằm thông báo cho người dùng về các sự kiện quan trọng trong trò chơi.