Một trong những điều chỉnh lớn nhất đối với người dùng mới sử dụng DynamoDB và NoSQL là cách lập mô hình dữ liệu để lọc trên toàn bộ tập dữ liệu. Ví dụ: Trong trò chơi của chúng ta, chúng ta cần tìm các phiên trò chơi có vị trí trống để cho người dùng thấy những phiên trò chơi họ có thể tham gia.
Trong cơ sở dữ liệu quan hệ, bạn sẽ viết một số SQL để truy vấn dữ liệu.

SELECT * FROM games
	WHERE status = “OPEN”

DynamoDB có thể lọc kết quả trên thao tác Query hoặc Scan, nhưng DynamoDB không hoạt động giống như cơ sở dữ liệu quan hệ. Bộ lọc DynamoDB sẽ áp dụng sau khi các mục ban đầu khớp với thao tác Query hoặc Scan được truy xuất. Bộ lọc làm giảm kích thước của tải trọng được gửi từ dịch vụ DynamoDB nhưng số lượng mục được truy xuất ban đầu phụ thuộc vào giới hạn kích thước của DynamoDB.

Rất may là có một số cách để bạn cho phép sử dụng truy vấn được lọc với tập dữ liệu trong DynamoDB. Để cung cấp bộ lọc hiệu quả cho bảng DynamoDB, bạn cần lập kế hoạch đưa các bộ lọc vào mô hình dữ liệu của bảng ngay từ đầu. Hãy nhớ bài học chúng ta đã học trong mô-đun thứ hai của phòng thực hành này: Xem xét các mẫu hình truy cập, rồi thiết kế bảng của bạn.

Trong các bước sau, chúng ta sử dụng chỉ mục phụ toàn bộ để tìm các trò chơi đang mở. Cụ thể, chúng ta sẽ sử dụng kỹ thuật chỉ mục thưa để xử lý mẫu hình truy cập này.

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


  • Bước 1: Lập mô hình chỉ mục phụ thưa

    Chỉ mục phụ là công cụ lập mô hình dữ liệu quan trọng trong DynamoDB. Chúng cho phép bạn định hình lại dữ liệu để có thể sử dụng các mẫu hình truy vấn thay thế. Để tạo chỉ mục phụ, bạn phải chỉ định khóa chính của chỉ mục, giống như khi bạn tạo bảng trước đó. Lưu ý rằng khóa chính cho chỉ mục phụ toàn bộ không nhất thiết phải là duy nhất cho mỗi mục. Sau đó, DynamoDB sẽ sao chép các mục vào chỉ mục dựa trên các thuộc tính được chỉ định và bạn có thể truy vấn chỉ mục giống như truy vấn bảng.

    Sử dụng chỉ mục phụ thưa là chiến lược nâng cao trong DynamoDB. Với chỉ mục phụ, DynamoDB chỉ sao chép các mục từ bảng gốc khi chúng có các phần tử của khóa chính trong chỉ mục phụ. Các mục không có các phần tử khóa chính sẽ không được sao chép, đó là lý do tại sao các chỉ mục phụ này được gọi là “thưa”.

    Hãy cùng xem điều này mang lại gì cho chúng ta. Có lẽ bạn vẫn nhớ rằng chúng ta có hai mẫu hình truy cập để tìm các trò chơi đang mở:

    • Tìm các trò chơi đang mở (Đọc)
    • Tìm các trò chơi đang mở theo bản đồ (Đọc)

    Chúng ta có thể tạo chỉ mục phụ bằng cách sử dụng khóa chính tổng hợp, trong đó khóa HASH là thuộc tính map của trò chơi và khóa RANGE là thuộc tính open_timestamp của trò chơi, cho biết thời gian trò chơi đã được mở.

    Điểm quan trọng với chúng ta là khi trò chơi trở nên đầy, thuộc tính open_timestamp sẽ bị xóa. Khi thuộc tính này bị xóa, trò chơi đã đầy sẽ bị xóa khỏi chỉ mục phụ vì nó không có giá trị cho thuộc tính khóa RANGE. Đây là điều giữ cho chỉ số của chúng ta thưa: Chỉ mục chỉ bao gồm các trò chơi đang mở có thuộc tính open_timestamp.

    Trong bước tiếp theo, chúng ta sẽ tạo chỉ mục phụ.

  • Bước 2: Tạo chỉ mục phụ thưa

    Trong bước này, chúng ta sẽ tạo chỉ mục phụ thưa cho các trò chơi đang mở (trò chơi chưa đầy).

    Hoạt động tạo chỉ mục phụ tương tự như việc tạo bảng. Trong mã đã tải xuống, bạn sẽ tìm thấy tệp tập lệnh trong thư mục scripts/ có tên add_secondary_index.py. Nội dung của tệp đó như sau.

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.update_table(
            TableName='battle-royale',
            AttributeDefinitions=[
                {
                    "AttributeName": "map",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "open_timestamp",
                    "AttributeType": "S"
                }
            ],
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        "IndexName": "OpenGamesIndex",
                        "KeySchema": [
                            {
                                "AttributeName": "map",
                                "KeyType": "HASH"
                            },
                            {
                                "AttributeName": "open_timestamp",
                                "KeyType": "RANGE"
                            }
                        ],
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 1,
                            "WriteCapacityUnits": 1
                        }
                    }
                }
            ],
        )
        print("Table updated successfully.")
    except Exception as e:
        print("Could not update table. Error:")
        print(e)

    Tất cả thuộc tính được sử dụng trong khóa chính cho chỉ mục phụ hoặc bảng phải được định nghĩa trong AttributeDefinitions. Sau đó, chúng ta Create chỉ mục phụ mới trong thuộc tính GlobalSecondaryIndexUpdates. Đối với chỉ mục phụ này, chúng ta chỉ định tên chỉ mục, lược đồ của khóa chính, thông lượng được cung cấp và các thuộc tính mà chúng ta muốn diễn đạt.

    Lưu ý: Chúng ta không phải xác định rằng chỉ mục phụ này được dự định sử dụng làm chỉ mục thưa. Đó đơn thuần là chức năng của dữ liệu mà bạn đưa vào. Nếu bạn ghi các mục vào bảng không có thuộc tính cho chỉ mục phụ, các mục này sẽ không được bao gồm trong chỉ mục phụ của bạn.

    Tạo chỉ mục phụ bằng cách chạy lệnh sau.

    python scripts/add_secondary_index.py

    Bạn sẽ thấy thông báo sau trong bảng điều khiển: “Table updated successfully” (Đã cập nhật bảng thành công).

    Trong bước tiếp theo, chúng ta sẽ sử dụng chỉ mục thưa để tìm các trò chơi đang mở theo bản đồ.

  • Bước 3: Truy vấn chỉ mục phụ thưa

    Chúng ta đã đặt cấu hình cho chỉ mục phụ, giờ thì hãy sử dụng chỉ mục phụ này để đáp ứng một số mẫu hình truy cập.

    Để sử dụng chỉ mục phụ, bạn có hai lệnh gọi API: QueryScan. Với Query, bạn phải chỉ định khóa HASH và khóa này sẽ trả về kết quả đích. Với Scan, bạn không phải chỉ định khóa HASH và thao tác này sẽ chạy trên toàn bộ bảng. Không khuyến khích thực hiện Scan trong DynamoDB, trừ các trường hợp cụ thể vì thao tác này truy cập vào mọi mục trong cơ sở dữ liệu của bạn. Nếu bạn có lượng dữ liệu đáng kể trong bảng, việc quét có thể mất rất nhiều thời gian. Trong bước tiếp theo, chúng tôi sẽ cho bạn thấy lý do tại sao Scan có thể là công cụ mạnh mẽ khi được sử dụng với các chỉ mục thưa.

    Chúng ta có thể sử dụng API Query với chỉ mục phụ đã tạo ở bước trước để tìm tất cả các trò chơi đang mở theo tên bản đồ. Chỉ mục phụ được phân vùng theo tên bản đồ, cho phép chúng ta thực hiện các truy vấn hướng đích để tìm các trò chơi đang mở.

    Trong mã bạn tải xuống, tệp find_open_games_by_map.py nằm ở thư mục application/. Sau đây là nội dung của tập lệnh này.

    import boto3
    
    from entities import Game
    
    dynamodb = boto3.client('dynamodb')
    
    def find_open_games_by_map(map_name):
        resp = dynamodb.query(
            TableName='battle-royale',
            IndexName="OpenGamesIndex",
            KeyConditionExpression="#map = :map",
            ExpressionAttributeNames={
                "#map": "map"
            },
            ExpressionAttributeValues={
                ":map": { "S": map_name },
            },
            ScanIndexForward=True
        )
    
        games = [Game(item) for item in resp['Items']]
    
        return games
    
    games = find_open_games_by_map("Green Grasslands")
    for game in games:
        print(game)

    Trong tập lệnh trước, hàm find_open_games_by_map tương tự như hàm mà bạn sẽ có trong ứng dụng của mình. Hàm này chấp nhận tên bản đồ và thực hiện truy vấn với OpenGamesIndex để tìm tất cả các trò chơi đang mở cho bản đồ. Sau đó, hàm này sẽ tập hợp các thực thể được trả về thành các đối tượng Game mà ứng dụng của bạn có thể sử dụng.

    Thực thi tập lệnh này bằng cách chạy lệnh sau trong terminal của bạn.

    python application/find_open_games_by_map.py

    Terminal sẽ hiển thị đầu ra sau đây với bốn trò chơi đang mở cho bản đồ Green Grasslands.

    Open games for Green Grasslands:
    Game<14c7f97e-8354-4ddf-985f-074970818215 -- Green Grasslands>
    Game<3d4285f0-e52b-401a-a59b-112b38c4a26b -- Green Grasslands>
    Game<683680f0-02b0-4e5e-a36a-be4e00fc93f3 -- Green Grasslands>
    Game<0ab37cf1-fc60-4d93-b72b-89335f759581 -- Green Grasslands>
    sudo cp -r wordpress/* /var/www/html/

    Trong bước tiếp theo, chúng ta sẽ sử dụng API Scan để quét chỉ mục phụ thưa.

  • Bước 4: Quét chỉ mục phụ thưa

    Ở bước trước, chúng ta đã xem cách tìm các trò chơi cho một bản đồ cụ thể. Một số người chơi có thể thích chơi một bản đồ cụ thể, vì vậy điều này rất hữu ích. Những người chơi khác có thể sẵn lòng chơi trò chơi ở bất kỳ bản đồ nào. Trong phần này, chúng tôi sẽ trình bày cách tìm bất kỳ trò chơi nào đang mở trong ứng dụng, bất kể loại bản đồ. Để thực hiện việc này, chúng ta sử dụng API Scan.

    Nhìn chung, bạn không nên thiết kế bảng để sử dụng thao tác Scan của DynamoDB vì DynamoDB được xây dựng cho các truy vấn chính xác sẽ lấy các thực thể chính xác mà bạn cần. Thao tác Scan sẽ lấy một tập hợp ngẫu nhiên các thực thể trên bảng của bạn, vì vậy việc tìm các thực thể bạn cần có thể yêu cầu nhiều quy trình khép kín đến cơ sở dữ liệu.

    Tuy nhiên, đôi khi Scan có thể rất hữu ích. Trong tình huống của chúng ta, chúng ta có một chỉ mục phụ thưa, có nghĩa là chỉ mục của chúng ta không nên có nhiều thực thể như vậy. Ngoài ra, chỉ mục này chỉ bao gồm những trò chơi đang mở và đó chính xác là những gì chúng ta cần.

    Scan rất phù hợp với trường hợp sử dụng này. Chúng ta hãy cùng xem cách thức hoạt động. Trong mã bạn đã tải xuống, tệp find_open_games.py nằm ở thư mục application/. Sau đây là nội dung của tệp này.

    import boto3
    
    from entities import Game
    
    dynamodb = boto3.client('dynamodb')
    
    def find_open_games():
        resp = dynamodb.scan(
            TableName='battle-royale',
            IndexName="OpenGamesIndex",
        )
    
        games = [Game(item) for item in resp['Items']]
    
        return games
    
    games = find_open_games()
    print("Open games:")
    for game in games:
        print(game)

    Mã này tương tự như mã trong bước trước. Tuy nhiên, thay vì sử dụng phương pháp query() trên máy khách DynamoDB, chúng ta sử dụng phương pháp scan(). Vì sử dụng scan() nên chúng ta không cần chỉ định bất cứ điều gì về các điều kiện của khóa như đã làm với query(). Chúng ta chỉ yêu cầu DynamoDB trả về một loạt các mục không theo thứ tự cụ thể.

    Chạy tập lệnh này với lệnh sau trong terminal của bạn.

    python application/find_open_games.py

    Terminal của bạn sẽ in một danh sách chín trò chơi đang mở trên nhiều bản đồ.

    Open games:
    Game<c6f38a6a-d1c5-4bdf-8468-24692ccc4646 -- Urban Underground>
    Game<d06af94a-2363-441d-a69b-49e3f85e748a -- Dirty Desert>
    Game<873aaf13-0847-4661-ba26-21e0c66ebe64 -- Dirty Desert>
    Game<fe89e561-8a93-4e08-84d8-efa88bef383d -- Dirty Desert>
    Game<248dd9ef-6b17-42f0-9567-2cbd3dd63174 -- Juicy Jungle>
    Game<14c7f97e-8354-4ddf-985f-074970818215 -- Green Grasslands>
    Game<3d4285f0-e52b-401a-a59b-112b38c4a26b -- Green Grasslands>
    Game<683680f0-02b0-4e5e-a36a-be4e00fc93f3 -- Green Grasslands>
    Game<0ab37cf1-fc60-4d93-b72b-89335f759581 -- Green Grasslands>
    

    Trong bước này, chúng ta đã thấy việc sử dụng thao tác Scan có thể là lựa chọn phù hợp như thế nào trong một số trường hợp cụ thể. Chúng ta đã sử dụng Scan để lấy một tổ hợp thực thể từ chỉ mục phụ thưa để hiển thị các trò chơi đang mở cho người chơi.

    Trong các bước tiếp theo, chúng ta sẽ đáp ứng hai mẫu hình truy cập:

    • Tham gia trò chơi cho người dùng (Ghi)
    • Bắt đầu trò chơi (Ghi)

    Để đáp ứng mẫu hình truy cập “Tham gia trò chơi cho người dùng” trong các bước sau, chúng ta sẽ sử dụng giao dịch DynamoDB. Giao dịch rất phổ biến trong các hệ thống quan hệ với những thao tác ảnh hưởng đến nhiều phần tử dữ liệu cùng một lúc. Ví dụ: Hãy tưởng tượng bạn đang điều hành một ngân hàng. Một khách hàng, Alejandra, chuyển 100 USD cho một khách hàng khác là Ana. Khi ghi lại giao dịch này, bạn sẽ sử dụng một giao dịch để đảm bảo áp dụng thay đổi cho số dư của cả hai khách hàng thay vì chỉ một khách hàng.

    Giao dịch DynamoDB giúp bạn dễ dàng hơn trong việc xây dựng các ứng dụng có thể thay đổi nhiều mục khi thực hiện một thao tác. Với giao dịch, bạn có thể thao tác trên tối đa 10 mục khi thực hiện một yêu cầu giao dịch.

    Trong lệnh gọi API TransactWriteItem, bạn có thể sử dụng các thao tác sau:

    • Put: Để chèn hoặc ghi đè một mục.
    • Update: Để cập nhật mục hiện có.
    • Delete: Để xóa một mục.
    • ConditionCheck: Để xác nhận một điều kiện trên mục hiện có mà không làm thay đổi mục đó.

     

    Trong bước tiếp theo, chúng ta sẽ sử dụng giao dịch DynamoDB khi thêm người dùng mới vào trò chơi trong khi ngăn trò chơi bị quá tải.

  • Bước 5: Thêm người dùng vào trò chơi

    Mẫu hình truy cập đầu tiên chúng ta xử lý trong mô-đun này là thêm người dùng mới vào trò chơi.

    Khi thêm người dùng mới vào trò chơi, chúng ta cần:

    • Xác nhận rằng chưa có đủ 50 người chơi trong trò chơi (mỗi trò chơi có thể có tối đa 50 người chơi).
    • Xác nhận rằng người dùng chưa tham gia trò chơi.
    • Tạo thực thể UserGameMapping mới để thêm người dùng vào trò chơi.
    • Tăng thuộc tính people trên thực thể Game để theo dõi có bao nhiêu người chơi trong trò chơi.

    Lưu ý rằng việc hoàn thành tất cả những điều này đòi hỏi thực hiện thao tác ghi trên thực thể Game hiện có và thực thể UserGameMapping mới cũng như logic điều kiện cho từng thực thể. Đây là loại thao tác phù hợp hoàn hảo cho các giao dịch DynamoDB vì bạn cần phải làm việc trên nhiều thực thể trong cùng một yêu cầu và bạn muốn toàn bộ yêu cầu đó cùng thành công hoặc thất bại.

    Trong mã bạn đã tải xuống, tập lệnh join_game.py nằm ở thư mục application/. Hàm trong tập lệnh đó sử dụng giao dịch DynamoDB để thêm người dùng vào trò chơi.

    Sau đây là nội dung của tập lệnh.

    import boto3
    
    from entities import Game, UserGameMapping
    
    dynamodb = boto3.client('dynamodb')
    
    GAME_ID = "c6f38a6a-d1c5-4bdf-8468-24692ccc4646"
    USERNAME = 'vlopez'
    
    
    def join_game_for_user(game_id, username):
        try:
            resp = dynamodb.transact_write_items(
                TransactItems=[
                    {
                        "Put": {
                            "TableName": "battle-royale",
                            "Item": {
                                "PK": {"S": "GAME#{}".format(game_id) },
                                "SK": {"S": "USER#{}".format(username) },
                                "game_id": {"S": game_id },
                                "username": {"S": username }
                            },
                            "ConditionExpression": "attribute_not_exists(SK)",
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD"
                        },
                    },
                    {
                        "Update": {
                            "TableName": "battle-royale",
                            "Key": {
                                "PK": { "S": "GAME#{}".format(game_id) },
                                "SK": { "S": "#METADATA#{}".format(game_id) },
                            },
                            "UpdateExpression": "SET people = people + :p",
                            "ConditionExpression": "people <= :limit",
                            "ExpressionAttributeValues": {
                                ":p": { "N": "1" },
                                ":limit": { "N": "50" }
                            },
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD"
                        }
                    }
                ]
            )
            print("Added {} to game {}".format(username, game_id))
            return True
        except Exception as e:
            print("Could not add user to game")
    
    join_game_for_user(GAME_ID, USERNAME)

    Trong hàm join_game_for_user của tập lệnh này, phương pháp transact_write_items() sẽ thực hiện giao dịch ghi. Giao dịch này có hai thao tác.

    Trong thao tác đầu tiên của giao dịch, chúng ta sử dụng thao tác Put để chèn thực thể UserGameMapping mới. Trong thao tác đó, chúng ta sẽ chỉ định điều kiện là thực thể này không có thuộc tính SK. Điều này đảm bảo rằng thực thể có PKSK này không tồn tại. Nếu đã tồn tại một thực thể như vậy thì nghĩa là người dùng này đã tham gia trò chơi.

    Thao tác thứ hai là thao tác Update trên thực thể Game để tăng thuộc tính people lên một giá trị. Trong thao tác này, chúng ta thêm kiểm tra điều kiện rằng giá trị hiện tại của people không lớn hơn 50. Ngay khi có 50 người tham gia trò chơi, trò chơi đã đầy và sẵn sàng để bắt đầu.

    Chạy tập lệnh này với lệnh sau trong terminal của bạn.

    python application/join_game.py

    Đầu ra trong terminal sẽ cho biết rằng người dùng đã được thêm vào trò chơi.

    Added vlopez to game c6f38a6a-d1c5-4bdf-8468-24692ccc4646

    Lưu ý rằng nếu bạn cố chạy lại tập lệnh, hàm sẽ thất bại. Người dùng vlopez đã được thêm vào trò chơi, vì vậy việc cố gắng thêm người dùng này một lần nữa không đáp ứng các điều kiện chúng ta đã chỉ định.

    Việc bổ sung giao dịch DynamoDB sẽ đơn giản hóa rất nhiều cho quy trình liên quan đến các thao tác phức tạp như thế này. Nếu không có giao dịch, quy trình này sẽ yêu cầu nhiều lệnh gọi API với các điều kiện phức tạp và quay lui thủ công trong trường hợp có xung đột. Bây giờ, chúng ta có thể thực hiện các thao tác phức tạp như vậy với chưa tới 50 dòng mã.

    Trong bước tiếp theo, chúng ta sẽ xử lý mẫu truy cập “Bắt đầu trò chơi (Ghi)”.

  • Bước 6: Bắt đầu trò chơi

    Ngay khi trò chơi có 50 người dùng, người tạo trò chơi có thể bắt đầu trò chơi để khởi động màn chơi. Trong bước này, chúng ta sẽ xem cách xử lý mẫu hình truy cập này.

    Khi backend ứng dụng nhận được yêu cầu bắt đầu trò chơi, chúng ta sẽ kiểm tra ba điều:

    • Trò chơi đã có 50 người đăng ký.
    • Người dùng yêu cầu là người tạo trò chơi.
    • Trò chơi chưa bắt đầu.

    Chúng ta có thể xử lý từng kiểm tra này trong một biểu thức điều kiện ở yêu cầu cập nhật trò chơi. Nếu đạt tất cả các kiểm tra này, chúng ta cần cập nhật thực thể của mình theo các cách sau:

    • Xóa thuộc tính open_timestamp để thuộc tính này không xuất hiện dưới dạng trò chơi đang mở trong chỉ mục phụ thưa từ mô-đun trước.
    • Thêm thuộc tính start_time để cho biết thời điểm trò chơi bắt đầu.

    Trong mã bạn đã tải xuống, tập lệnh start_game.py nằm ở thư mục application/. Sau đây là nội dung của tệp.

    import datetime
    
    import boto3
    
    from entities import Game
    
    dynamodb = boto3.client('dynamodb')
    
    GAME_ID = "c6f38a6a-d1c5-4bdf-8468-24692ccc4646"
    CREATOR = "gstanley"
    
    def start_game(game_id, requesting_user, start_time):
        try:
            resp = dynamodb.update_item(
                TableName='battle-royale',
                Key={
                    "PK": { "S": "GAME#{}".format(game_id) },
                    "SK": { "S": "#METADATA#{}".format(game_id) }
                },
                UpdateExpression="REMOVE open_timestamp SET start_time = :time",
                ConditionExpression="people = :limit AND creator = :requesting_user AND attribute_not_exists(start_time)",
                ExpressionAttributeValues={
                    ":time": { "S": start_time.isoformat() },
                    ":limit": { "N": "50" },
                    ":requesting_user": { "S": requesting_user }
                },
                ReturnValues="ALL_NEW"
            )
            return Game(resp['Attributes'])
        except Exception as e:
            print('Could not start game')
            return False
    
    game = start_game(GAME_ID, CREATOR, datetime.datetime(2019, 4, 16, 10, 15, 35))
    
    if game:
        print("Started game: {}".format(game))

    Trong tập lệnh này, hàm start_game tương tự như hàm bạn sẽ có trong ứng dụng của mình. Hàm này nhận game_id, requesting_userstart_time, sau đó sẽ chạy yêu cầu cập nhật thực thể Game để bắt đầu trò chơi.

    Tham số ConditionExpression trong lệnh gọi update_item() chỉ định từng kiểm tra trong số ba kiểm tra mà chúng ta đã liệt kê trước đó trong bước này—trò chơi phải có 50 người, người dùng yêu cầu bắt đầu trò chơi phải là người tạo trò chơi và trò chơi không được có thuộc tính start_time cho biết trò chơi đã bắt đầu.

    Trong tham số UpdateExpression, bạn có thể thấy những thay đổi mà chúng ta muốn thực hiện đối với thực thể của mình. Đầu tiên, chúng ta xóa thuộc tính open_timestamp khỏi thực thể, rồi đặt thuộc tính start_time thành thời gian bắt đầu trò chơi.

    Chạy tập lệnh này với lệnh sau trong terminal của bạn.

    python application/start_game.py

    Bạn sẽ thấy kết quả đầu ra trong terminal cho biết trò chơi đã được bắt đầu thành công.

    Started game: Game<c6f38a6a-d1c5-4bdf-8468-24692ccc4646 -- Urban Underground>

    Cố gắng chạy tập lệnh lần nữa trong terminal của bạn. Lần này, bạn sẽ thấy thông báo lỗi cho biết bạn không thể bắt đầu trò chơi. Đó là vì bạn đã bắt đầu trò chơi, vì vậy thuộc tính start_time đã tồn tại. Kết quả là, yêu cầu không đáp ứng kiểm tra điều kiện trên thực thể.

    Bạn có thể nhớ lại rằng giữa thực thể Game và các thực thể User được liên kết có mối quan hệ nhiều - nhiều và mối quan hệ này được thể hiện bằng thực thể UserGameMapping.

    Thông thường, bạn muốn truy vấn cả hai phía của mối quan hệ. Với thiết lập khóa chính, chúng ta có thể tìm tất cả thực thể User trong Game. Chúng ta có thể cho phép truy vấn tất cả các thực thể Game cho User bằng cách sử dụng chỉ mục đảo ngược.

    Trong DynamoDB, chỉ mục đảo ngược là chỉ mục phụ nghịch đảo với khóa chính của bạn. Khóa RANGE trở thành khóa HASH và ngược lại. Mẫu hình này sẽ lật ngược bảng của bạn và cho phép bạn truy vấn ở phía bên kia của mối quan hệ nhiều - nhiều.

    Trong các bước sau, chúng ta sẽ thêm chỉ mục đảo ngược vào bảng và xem cách sử dụng chỉ mục này để truy xuất tất cả các thực thể Game cho một User cụ thể. 

  • Bước 7: Thêm chỉ mục đảo ngược

    Trong bước này, chúng ta sẽ thêm chỉ mục đảo ngược vào bảng. Chỉ mục đảo ngược được tạo như bất kỳ chỉ mục phụ nào khác.

    Trong mã bạn đã tải xuống, tập lệnh add_inverted_index.py nằm trong thư mục scripts/. Tập lệnh Python này sẽ thêm chỉ mục đảo ngược vào bảng.

    Nội dung của tệp đó như sau.

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.update_table(
            TableName='battle-royale',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        "IndexName": "InvertedIndex",
                        "KeySchema": [
                            {
                                "AttributeName": "SK",
                                "KeyType": "HASH"
                            },
                            {
                                "AttributeName": "PK",
                                "KeyType": "RANGE"
                            }
                        ],
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 1,
                            "WriteCapacityUnits": 1
                        }
                    }
                }
            ],
        )
        print("Table updated successfully.")
    except Exception as e:
        print("Could not update table. Error:")
        print(e)
    

    Trong tập lệnh này, chúng ta sẽ gọi phương pháp update_table() trên máy khách DynamoDB. Trong phương pháp này, chúng ta chuyển chi tiết về chỉ mục phụ mà chúng ta muốn tạo, bao gồm lược đồ chính cho chỉ mục, thông lượng được cung cấp và các thuộc tính để diễn đạt trong chỉ mục.

    Chạy tập lệnh này bằng cách nhập lệnh sau trong terminal của bạn.

    python scripts/add_inverted_index.py

    Terminal sẽ hiển thị đầu ra là chỉ mục đã được tạo thành công.

    Table updated successfully.

    Trong bước tiếp theo, chúng ta sẽ sử dụng chỉ mục đảo ngược để truy xuất tất cả các thực thể Game cho một User cụ thể.

  • Bước 8: Truy xuất trò chơi cho người dùng

    Chúng ta đã tạo chỉ mục đảo ngược, giờ thì hãy sử dụng chỉ mục này để truy xuất các thực thể Game mà một User chơi. Để xử lý vấn đề này, chúng ta cần truy vấn chỉ mục đảo ngược với User có thực thể Game mà chúng ta muốn xem.

    Trong mã bạn đã tải xuống, tập lệnh find_games_for_user.py nằm ở thư mục application/. Sau đây là nội dung của tệp.

    import boto3
    
    from entities import UserGameMapping
    
    dynamodb = boto3.client('dynamodb')
    
    USERNAME = "carrpatrick"
    
    
    def find_games_for_user(username):
        try:
            resp = dynamodb.query(
                TableName='battle-royale',
                IndexName='InvertedIndex',
                KeyConditionExpression="SK = :sk",
                ExpressionAttributeValues={
                    ":sk": { "S": "USER#{}".format(username) }
                },
                ScanIndexForward=True
            )
        except Exception as e:
            print('Index is still backfilling. Please try again in a moment.')
            return None
    
        return [UserGameMapping(item) for item in resp['Items']]
    
    games = find_games_for_user(USERNAME)
    
    if games:
        print("Games played by {}:".format(USERNAME))
        for game in games:
            print(game)
    

    Trong tập lệnh này, chúng ta có một hàm gọi là find_games_for_user() tương tự như hàm mà bạn sẽ có trong trò chơi của mình. Hàm này nhận tên người dùng và trả về tất cả các trò chơi mà người dùng đã cho chơi.

    Chạy tập lệnh này với lệnh sau trong terminal của bạn.

    python application/find_games_for_user.py

    Tập lệnh sẽ in tất cả các trò chơi do người dùng carrpatrick chơi.

    Games played by carrpatrick:
    UserGameMapping<25cec5bf-e498-483e-9a00-a5f93b9ea7c7 -- carrpatrick -- SILVER>
    UserGameMapping<c6f38a6a-d1c5-4bdf-8468-24692ccc4646 -- carrpatrick>
    UserGameMapping<c9c3917e-30f3-4ba4-82c4-2e9a0e4d1cfd -- carrpatrick>
    

    Trong mô-đun này, chúng ta đã thêm chỉ mục phụ vào bảng. Điều này thỏa mãn hai mẫu hình truy cập bổ sung:

    • Tìm các trò chơi đang mở theo bản đồ (Đọc)
    • Tìm các trò chơi đang mở (Đọc)

    Để thực hiện điều này, chúng ta đã sử dụng chỉ mục thưa chỉ bao gồm các trò chơi vẫn đang mở cho người chơi bổ sung. Sau đó, chúng ta đã sử dụng cả API QueryScan với chỉ mục để tìm các trò chơi đang mở.

    Ngoài ra, chúng ta đã xem cách đáp ứng hai thao tác ghi nâng cao trong ứng dụng. Đầu tiên, chúng ta sử dụng giao dịch DynamoDB khi người dùng tham gia trò chơi. Với giao dịch, chúng ta đã xử lý lệnh ghi có điều kiện phức tạp trên nhiều thực thể trong một yêu cầu.

    Thứ hai, chúng ta đã triển khai hàm cho người tạo trò chơi bắt đầu trò chơi khi trò chơi sẵn sàng. Trong mẫu hình truy cập này, chúng ta có thao tác cập nhật yêu cầu kiểm tra giá trị của ba thuộc tính và cập nhật hai thuộc tính. Bạn có thể diễn đạt logic phức tạp này trong một yêu cầu thông qua sức mạnh của biểu thức điều kiện và biểu thức cập nhật.

    Thứ ba, chúng ta đã đáp ứng mẫu hình truy cập cuối cùng bằng cách truy xuất tất cả các thực thể Game do User chơi. Để xử lý mẫu hình truy cập này, chúng ta đã tạo chỉ mục phụ bằng cách sử dụng mẫu hình chỉ mục đảo ngược để cho phép truy vấn ở phía bên kia của mối quan hệ nhiều - nhiều giữa các thực thể UserGame.

    Trong mô-đun tiếp theo, chúng ta sẽ dọn dẹp các tài nguyên mà chúng ta đã tạo.