หนึ่งในการปรับเปลี่ยนที่ใหญ่ที่สุดสำหรับผู้ใช้ที่ยังใหม่สำหรับ DynamoDB และ NoSQL คือวิธีการทำโมเดลข้อมูลเพื่อกรองฐานข้อมูลทั้งหมด ตัวอย่างเช่น ในเกมของเรา เราต้องหาเซสชันของเกมที่เปิดอยู่เพื่อแสดงให้ผู้ใช้เห็นว่าสามารถเข้าร่วมเกมใดได้บ้าง
ในฐานข้อมูลเชิงสัมพันธ์ คุณจะได้เขีบน SQL บางรูปแบบเพื่อสืบค้นข้อมูล

SELECT * FROM games
	WHERE status = “OPEN”

DynamoDB สามารถกรองผลลัพธ์ใน การสืบค้น หรือ การสแกน ได้ แต่ DynamoDB จะไม่มีการทำงานเหมือนฐานข้อมูลทั่วไป ตัวกรอง DynamoDB จะใช้หลังจากที่รายการเริ่มต้นมีค่าตรงกับที่ได้รับจาก การสืบค้น หรือ การสแกน ตัวกรองจะช่วยลดขนาดของเพย์โหลดที่ส่งมาจากบริการ DynamoDB แต่จำนวนรายการที่ได้รับในขั้นต้นจะเป็นไปตามข้อจำกัดด้านขนาดของ DynamoDB

โชคดีที่มีวิธีมากมายที่คุณสามารถทำการสืบค้นอย่างมีการคัดกรองกับฐานข้อมูลของคุณได้ใน DynamoDB เพื่อให้ได้การกรองข้อมูลอย่างมีประสิทธิภาพกับตาราง DynamoDB คุณต้องวางแผนการกรองข้อมูลลงในโมเดลข้อมูลของตารางตั้งแต่เริ่มต้น จดจำบทเรียนที่ผ่านการเรียนรู้มาแล้วในโมดูล 2 ของแล็บนี้ พิจารณารูปแบบการเข้าถึง แล้วจึงออกแบบตารางของคุณ

ในขั้นตอนดังต่อไปนี้ เราจะใช้ดัชนีรองรวมเพื่อค้นหาเกมที่เปิดอยู่ เราจะใช้เทคนิคดัชนีแบบกระจาย เพื่อจัดการกับรูปแบบการเข้าถึงนี้เป็นพิเศษ

ระยะเวลาที่ใช้ในการศึกษาโมดูล: 40 นาที


  • ขั้นตอนที่ 1: ทำโมเดลดัชนีรองแบบกระจาย

    ดัชนีรองคือเครื่องมือในการทำโมเดลข้อมูลที่สำคัญใน DynamoDB ซึ่งจะทำให้คุณสามารถเปลี่ยนรูปแบบของข้อมูลให้รองรับรูปแบบการสืบค้นอื่นๆ ได้ ในการสร้างดัชนีรอง คุณจะต้องกำหนดคีย์หลักของดัชนี เช่นเดียวกับที่คุณเคยทำไว้ในตาราง จำไว้ว่าคีย์หลักสำหรับดัชนีรองรวมไม่จำเป็นต้องมีความแตกต่างกันในแต่ละรายการ DynamoDB จะคัดลอกรายการลงในดัชนีตามแอตทริบิวต์ที่กำหนดไว้ และคุณสามารถสืบค้นได้เช่นเดียวกับในตาราง

    การใช้ดัชนีรองแบบกระจายเป็นกลยุทธ์ขั้นสูงใน DynamoDB ด้วยดัชนีรวม DynamoDB จะคัดลอกรายการจากตารางดั้งเดิมเฉพาะรายการที่มีองค์ประกอบของคีย์หลักในดัชนีรอง รายการที่ไม่มีองค์ประกอบของคีย์หลักจะไม่ถูกคัดลอก ซึ่งเป็นเหตุผลว่าทำไมดัชนีรองเหล่านี้จึงถูกเรียกว่า “แบบกระจาย”

    มาดูกันว่าสิ่งนี้ทำอะไรให้เราได้บ้าง คุณคงจำได้ว่าเรามีรูปแบบการเข้าถึง 2 รูปแบบสำหรับการค้นหาเกมที่เปิดอยู่

    • ค้นหาเกมที่เปิดอยู่ (อ่าน)
    • ค้นหาเกมที่เปิดอยู่ตามแมป (อ่าน)

    เราสามารถสร้างดัชนีรองโดยใช้คีย์หลักผสมซึ่งมีคีย์ HASH เป็นแอตทริบิวต์ map สำหรับเกม และคีย์ RANGE เป็นแอตทริบิวต์ open_timestamp สำหรับเกม แสดงเวลาที่เกมถูกเปิดขึ้น

    ส่วนสำคัญสำหรับเราก็คือเมื่อเกมมีจำนวนผู้เล่นเต็มแล้ว แอตทริบิวต์ open_timestamp จะถูกลบไป เมื่อแอตทริบิวต์ถูกลบไปแล้ว เกมที่มีจำนวนผู้เล่นเต็มจะถูกลบออกจากดัชนีรองเนื่องจากไม่มีค่าสำหรับแอตทริบิวต์ของคีย์ RANGE ซึ่งนี่ช่วยกระจายดัชนีของเราเอาไว้ โดยจะรวมกันเฉพาะเกมที่มีแอตทริบิวต์ open_timestamp

    ในขั้นตอนถัดไป เราจะสร้างดัชนีรอง

  • ขั้นตอนที่ 2: สร้างดัชนีรองแบบกระจาย

    ในขั้นตอนนี้ เราจะสร้างดัชนีรองแบบกระจายสำหรับเกมที่เปิด (เกมซึ่งผู้เล่นยังไม่เต็ม)

    การสร้างดัชนี้รองมีความคล้ายคลึงกับการสร้างตาราง ในโค้ดที่ดาวน์โหลดมา คุณจะพบไฟล์สคริปต์ในไดเรกทอรี scripts/ ที่ชื่อว่า add_secondary_index.py เนื้อหาในไฟล์ดังกล่าวมีดังต่อไปนี้

    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)

    เมื่อมีการใช้แอตทริบิวต์ในคีย์หลักสำหรับตารางหรือดัชนีรอง จะต้องมีการกำหนดแอตทริบิวต์เหล่านั้นใน AttributeDefinitions จากนั้น เราจะสร้างดัชนีรองใหม่ในคุณสมบัติ GlobalSecondaryIndexUpdates สำหรับดัชนีรองนี้ เราจะกำหนดชื่อดัชนี สคีมาของคีย์หลัก ปริมาณการแสดงผลที่คาดไว้ และแอตทริบิวต์ที่เราต้องการสำหรับโปรเจ็กต์

    จำไว้ว่าเราไม่จำเป็นต้องกำหนดให้ดัชนีรองของเราต้องใช้เป็นดัชนีแบบกระจาย นั่นเป็นฟังก์ชันอย่างแท้จริงของข้อมูลที่ป้อนเข้าไป ถ้าคุณเขียนรายการลงในตารางซึ่งไม่มีแอตทริบิวต์สำหรับดัชนีรองของคุณ รายการเหล่านั้นจะไม่รวมอยู่ในดัชนีรองของคุณ

    สร้างดัชนีรองด้วยการทำงานของของคำสั่งดังต่อไปนี้

    python scripts/add_secondary_index.py

    ควรจะพบข้อความต่อไปนี้บนคอนโซล: “อัปเดตตารางเสร็จสมบูรณ์”

    ในขั้นตอนถัดไป เราจะใช้ดัชนีแบบกระจายเพื่อหาเกมที่เปิดอยู่ตามแมป

  • ขั้นตอนที่ 3: สืบค้นดัชนีรองแบบกระจาย

    ในตอนนี้เรามีดัชนีรองที่กำหนดค่าไว้แล้ว ให้เอามาใช้ตามรูปแบบการเข้าถึงบางแบบ

    เมื่อใช้ดัชนีรอง จะมีการเรียกใช้ API 2 ตัวพร้อมใช้งานได้แก่: การสืบค้น และ การสแกน ด้วย การสืบค้น คุณจะต้องกำหนดคีย์ HASH จึงจะให้ผลลัพธ์เป้าหมาย ด้วย การสแกน คุณไม่จำเป็นต้องกำหนดคีย์ HASH จึงจะทำงานในตารางทั้งหมด การสแกน จะไม่สามารถทำงานได้บน DynamoDB ยกเว้นในบางกรณี เนื่องจากจะเป็นการเข้าถึงทุกรายการในฐานข้อมูลของคุณ ถ้าคุณมีข้อมูลเป็นจำนวนมากในตารางของคุณ การสแกนอาจใช้ระยะเวลานาน ในขั้นตอนถัดไป เราจะแสดงให้เห็นว่าทำไม การสแกน จึงเป็นเครื่องมือที่มีประสิทธิภาพเมื่อใช้งานกับดัชนีแบบกระจาย

    เราสามารถใช้ API การสืบค้น กับดัชนีรองที่เราสร้างขึ้นในขึ้นในขั้นตอนที่แล้วเพื่อค้นหาเกมที่เปิดอยู่ทั้งหมดตามชื่อแมป ดัชนีรองที่แบ่งตามชื่อแมปช่วยให้เราทำการสืบค้นอย่างมีเป้าหมายเพื่อค้นหาเกมที่เปิดอยู่

    ในโค้ดที่ดาวน์โหลด จะมีไฟล์ชื่อ find_open_games_by_map.py อยู่ในไดเรกทอรี application/ เนื้อหาของสคริปต์นี้มีดังต่อไปนี้

    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)

    ในการใช้งานสคริปต์ ฟังก์ชัน find_open_games_by_map จะมีความคล้ายคลึงกับฟังก์ชันที่มีอยู่ในแอปพลิเคชันของคุณ ฟังก์ชันนี้จะรับชื่อแมปและทำการสืบค้นกับ OpenGamesIndex เพื่อค้นหาเกมที่เปิดอยู่ทั้งหมดของแมป แล้วจึงรวมเอนทิตีที่ได้กลับมาลงในอ็อบเจ็กต์ Game ซึ่งสามารถใช้ได้ในแอปพลิเคชันของคุณ

    สั่งการทำงานสคริปต์นี้โดยการใช้คำสั่งต่อไปนี้ในเทอร์มินัลของคุณ:

    python application/find_open_games_by_map.py

    เทอร์มินัลจะแสดงผลดังต่อไปนี้ด้วยเกมที่เปิดอยู่สำหรับแมป Green Grasslands จำนวน 4 เกม

    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/

    ในขั้นตอนถัดไป เราจะใช้ API การสแกน เพื่อสแกนดัชนีรองแบบกระจาย

  • ขั้นตอนที่ 4: สแกนดัชนีรองแบบกระจาย

    ในขั้นตอนก่อนหน้า เราได้เห็นวิธีการค้นหาเกมสำหรับบางแมป ผู้เล่นบางรายอาจต้องการที่จะเล่นในแมปที่ต้องการ สิ่งนี้จึงมีประโยชน์มาก ผู้เล่นอื่นๆ บางคนอาจต้องการที่จะเล่นเกมในแมปใดก็ได้ ในส่วนนี้ เราจะแสดงวิธีค้นหาเกมที่เปิดอยู่ในแอปพลิเคชันโดยไม่คำนึงว่าจะต้องเป็นแมปแบบใด การจะทำเช่นนี้ เราจะใช้ API การสแกน

    โดยทั่วไปแล้ว คุณไม่ต้องการที่จะออกแบบตารางเพื่อใช้กับ การสแกน ของ DynamoDB เนื่องจาก DynamoDB ถูกสร้างขึ้นเพื่อการสืบค้นเชิงลึกซึ่งจะจับเอาเฉพาะเอนทิตีที่คุณต้องการ การ สแกน จะจับกลุ่มเอนทิตีในตารางของคุณทั้งหมดโดยการสุ่ม ดังนั้นการค้นหาเอนทิตีที่คุณต้องการอาจต้องใช้เวลาในการค้นหาทั่วฐานข้อมูลหลายรอบ

    อย่างไรก็ตาม บางครั้ง การสแกน ก็สามารถเป็นประโยชน์ได้ ในสถานการณ์ของเรานี้ เราจะมีดัชนีรองแบบกระจาย ซึ่งหมายความว่าดัชนีของเราไม่ควรที่จะมีเอนทิตีจำนวนมาก นอกจากนี้ ดัชนีจะมีเฉพาะเกมที่เปิดอยู่ และนั่นคือสิ่งที่เราต้องการ

    สำหรับกรณีนี้ การสแกน จะทำงานได้อย่างดีเยี่ยม มาดูกันว่ามันทำงานอย่างไร ในโค้ดที่ดาวน์โหลด จะมีไฟล์ชื่อ find_open_games.py อยู่ในไดเรกทอรี application/ ตามด้วยเนื้อหาในไฟล์

    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)

    โค้ดนี้มีความคล้ายคลึงกับโค้ดในขั้นตอนก่อนหน้า อย่างไรก็ตาม แทนการใช้วิธี การสืบค้น() บนไคลเอ็นต์ของ DynamoDB เราจะใช้วิธี การสแกน() เนื่องจากเรากำลังใช้ การสแกน() เราจึงไม่จำเป็นต้องกำหนดเงื่อนไขของคีย์อย่างที่เราทำกับ การสืบค้น() เราจะได้เฉพาะรายการจำนวนมากที่ DynamoDB นำกลับมาให้โดยไม่มีการเรียงลำดับ

    รันสคริปต์ด้วยคำสั่งต่อไปนี้ในเทอร์มินัลของคุณ

    python application/find_open_games.py

    เทอร์มินัลควรแสดงรายการของเกม 9 รายการซึ่งเปิดอยู่ในแมปที่แตกต่างกัน

    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>
    

    ในขั้นตอนนี้ เราได้เห็นว่าการใช้ การสแกน สามารถเป็นตัวเลือกที่เหมาะสมในบางสถานการณ์ได้อย่างไร เราใช้ การสแกน เพื่อจับการจัดประเภทของเอนทิตีจากดัชนีรองแบบกระจายของเราเพื่อแสดงเกมที่เปิดอยู่ให้กับผู้เล่น

    ในขั้นตอนถัดไป เราจะทำตามรูปแบบการเข้าถึง 2 แบบ

    • ร่วมเล่นเกมสำหรับผู้ใช้ (เขียน)
    • เริ่มเกม (เขียน)

    เพื่อทำตามรูปแบบการเข้าถึง “ร่วมเล่นเกมสำหรับผู้ใช้” ในขั้นตอนต่อไปนี้ เราจะใช้ธุรกรรม DynamoDB ธุรกรรมนั้นเป็นที่นิยมในระบบเชิงสัมพันธ์สำหรับการทำงานซึ่งมีผลต่อองค์ประกอบข้อมูลเป็นจำนวนมากในครั้งเดียว ตัวอย่างเช่น ลองจินตนาการว่าคุณกำลังทำงานธนาคาร ลูกค้ารายหนึ่งชื่อ Alejandra โอนเงิน 100 USD ไปให้ลูกค้าอีกรายชื่อ Ana เมื่อบันทึกการทำธุรกรรมนี้ คุณจะใช้ธุรกรรมเพื่อให้แน่ใจว่ามีการเปลี่ยนแปลงในยอดเงินคงเหลือของลูกค้าทั้งสองคน ไม่ใช่เพียงหนึ่งคน

    ธุรกรรม DynamoDB ทำให้ง่ายต่อการสร้างแอปพลิเคชันซึ่งเปลี่ยนรายการที่หลากหลายให้เป็นส่วนหนึ่งของการดำเนินการเดียว ด้วยธุรกรรม คุณสามารถดำเนินการได้ถึง 10 รายการ ภายในคำขอการทำธุรกรรมครั้งเดียว

    ในการเรียกใช้ API TransactWriteItem คุณสามารถใช้การดำเนินการต่อไปนี้ได้

    • ใส่: สำหรับการแทรกหรือเขียนทับรายการ
    • อัปเดต: สำหรับการอัปเดตรายการที่มีอยู่
    • ลบ: สำหรับการลบรายการ
    • ตรวจสอบสภาวะ: สำหรับการประเมินสภาวะของรายการที่มีอยู่โดยไม่มีการเปลี่ยนแปลงรายการ

     

    ในขั้นตอนถัดไป เราจะใช้ธุรกรรม DynamoDB เมื่อเพิ่มผู้ใช้ใหม่เข้าสู่เกมในขณะที่ป้องกันไม่ให้เกมมีจำนวนผู้เล่นมากเกินไป

  • ขั้นตอนที่ 5: เพิ่มผู้ใช้เข้าสู่เกม

    รูปแบบการเข้าถึงแรกที่เรากล่าวถึงในโมดูลนี้คือการเพิ่มผู้ใช้ใหม่เข้าสู่เกม

    เมื่อต้องการเพิ่มผู้ใช้ใหม่เข้าสู่เกม เราจำเป็นต้อง:

    • ตรวจสอบว่ามีผู้เล่นไม่ถึง 50 คนในเกม (แต่ละเกมจะมีผู้เล่นได้สูงสุด 50 คน)
    • ให้แน่ใจว่าผู้ใช้ไม่ได้อยู่ในเกมอยู่แล้ว
    • สร้างเอนทิตี UserGameMapping ใหม่เพื่อเพิ่มผู้ใช้เข้าสู่เกม
    • เพิ่มแอตทริบิวต์ people ลงบนเอนทิตี Game เพื่อตรวจสอบว่ามีผู้เล่นจำนวนเท่าใดในเกม

    จำไว้ว่าการจะได้มาซึ่งสิ่งเหล่านี้จำเป็นต้องใช้การเขียนในทุกเอนทิตี Game ที่มีอยู่เดิมและเอนทิตี UserGameMapping ใหม่ รวมถึงตรรกะตามเงื่อนไขสำหรับแต่ละเอนทิตี การทำงานในประเภทนี้มีความเหมาะสมอย่างยิ่งสำหรับธุรกรรม DynamoDB เนื่องจากจำเป็นต้องทำงานบนเอนทิตีจำนวนมากในคำขอเดียว และคุณต้องให้คำขอเอนทิตีทั้งหมดสำเร็จหรือล้มเหลวไปด้วยกัน

    ในโค้ดที่ดาวน์โหลด สคริปต์ชื่อ join_game.py จะอยู่ในไดเรกทอรี application/ ฟังก์ชันในสคริปต์นั้นใช้ธุรกรรม DynamoDB เพื่อเพิ่มผู้ใช้เข้าสู่เกม

    ตามด้วยเนื้อหาของสคริปต์

    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)

    ในฟังก์ชัน join_game_for_user นี้ของสคริปต์ วิธี transact_write_items() จะทำการเขียนธุรกรรม ธุรกรรมนี้มี 2 การทำงาน

    ในการทำงานแรกของธุรกรรม เราจะใช้การใส่เพื่อแทรกเอนทิตี UserGameMapping ใหม่ ในส่วนหนึ่งของการทำงานนั้น เราจะกำหนดสภาวะซึ่งควรจะไม่มีแอตทริบิวต์ SK อยู่สำหรับเอนทิตีนี้ สิ่งนี้เพื่อให้มั่นใจว่าไม่มีเอนทิตี้ที่มี PK และ SK อยู่ก่อนแล้ว ถ้ามีเอนทิตีดังกล่าวอยู่แล้ว หมายความว่าผู้ใช้รายนี้ได้เข้าร่วมเกมอยู่ก่อนแล้ว

    การทำงานที่ 2 คือการอัปเดตกับเอนทิตีของ Game เพื่อเพิ่มแอตทริบิวต์ people ทีละหนึ่งรายการ ในส่วนหนึ่งของการทำงานนี้ เราจะเพิ่มการตรวจสอบสภาวะเพื่อดูว่าค่าปัจจุบันของ people ไม่มากไปกว่า 50 เมื่อมีผู้เล่นถึง 50 คนเข้าร่วมเกม จำนวนผู้เล่นจะเต็มและพร้อมที่จะเริ่มเล่น

    รันสคริปต์นี้ด้วยคำสั่งต่อไปนี้ในเทอร์มินัลของคุณ

    python application/join_game.py

    ผลที่ได้บนเทอร์มินัลควรแสดงว่าผู้ใช้ถูกเพิ่มเข้าสู่เกมแล้ว

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

    จำไว้ว่าถ้าคุณพยายามรันสคริปต์อีกครั้ง ฟังก์ชันจะล้มเหลว ผู้ใช้ vlopez ถูกเพิ่มเข้าสู่เกมแล้ว ดังนั้นการพยายามเพิ่มผู้ใช้อีกครั้งจะไม่เป็นไปตามเงื่อนไขที่กำหนดไว้

    การใช้ธุรกรรม DynamoDB เข้ามาเสริมช่วยลดความซับซ้อนของเวิร์กโฟลวในการทำงานที่มีความซับซ้อนเช่นนี้ หากไม่มีธุรกรรม การทำเช่นนี้จำเป็นต้องมีการเรียกใช้ API ที่มีเงื่อนไขซับซ้อนหลายรายการและการย้อนกลับการทำงานด้วยตนเองในกรณีที่เกิดข้อขัดแย้งกันขึ้น ตอนนี้ เราสามารถนำเงื่อนไขที่ซับซ้อนดังกล่าวมาใช้ด้วยโค้ดที่น้อยกว่า 50 บรรทัด

    ในขั้นตอนถัดไป เราจะใช้รูปแบบการเข้าถึง “เริ่มเกม (เขียน)”

  • ขั้นตอนที่ 6: เริ่มเกม

    ทันทีที่เกมมีผู้ใช้ถึง 50 คน ผู้สร้างเกมสามารถเริ่มเกมเพื่อเริ่มต้นการเล่นได้ ในขั้นตอนนี้ เราจะแสดงวิธีจัดการกับรูปแบบการเข้าถึงนี้

    เมื่อการทำงานส่วนหลังของแอปพลิเคชันได้รับคำขอเพื่อเริ่มเกม เราจะตรวจสอบ 3 สิ่ง

    • ต้องมีผู้ใช้ 50 คนลงชื่อเข้าสู่ระบบของเกม
    • ผู้ใช้ที่ส่งคำขอต้องเป็นผู้สร้างเกม
    • เกมต้องยังไม่เริ่ม

    เราสามารถตรวจสอบดังต่อไปนี้ได้ในการแสดงเงื่อนไขในคำขอเพื่ออัปเดตเกม ถ้าตรงตามเงื่อนไขทั้งหมด เราต้องอัปเดตเอนทิตีของเราด้วยวิธีดังต่อไปนี้:

    • ลบแอตทริบิวท์ open_timestamp ออกเพื่อไม่ให้ปรากฏขึ้นกับเกมที่เปิดอยู่ในดัชนีรองแบบกระจายจากโมดูลก่อนหน้า
    • เพิ่มแอตทริบิวต์ start_time เพื่อให้แสดงเมื่อเกมเริ่มต้นแล้ว

    ในโค้ดที่ดาวน์โหลด สคริปต์ชื่อ start_game.py จะอยู่ในไดเรกทอรี application/ เนื้อหาในไฟล์มีดังต่อไปนี้

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

    ในสคริปต์นี้ ฟังก์ชัน start_game มีความคล้ายคลึงกับฟังก์ชันที่มีในแอปพลิเคชันของคุณ โดยจะใช้ game_id, requesting_user และ start_time และรันคำขอเพื่ออัปเดตเอนทิตี Game เพื่อเริ่มเกม

    พารามิเตอร์ ConditionExpression ในการเรียกใช้ update_item() จะระบุแต่ละรายการตรวจสอบใน 3 รายการที่เราเตรียมไว้ก่อนขั้นตอนนี้ ได้แก่ เกมจะต้องมีผู้เล่น 50 คน ผู้ใช้ที่ส่งคำขอเริ่มเกมจะต้องเป็นผู้สร้างเกม และเกมจะต้องไม่มีแอตทริบิวต์ start_time ซึ่งเป็นตัวแสดงว่าเกมได้เริ่มไปแล้ว

    ในพารามิเตอร์ UpdateExpression คุณจะสามารถเห็นสิ่งที่คุณต้องการเปลี่ยนแปลงกับเอนทิตีได้ อันดับแรก ให้เราลบแอตทริบิวต์ open_timestamp ออกจากเอนทิตี แล้วให้เราตั้งแอตทริบิวต์ start_time ให้กับเวลาเริ่มเกม

    รันสคริปต์นี้ในเทอร์มินัลของคุณด้วยคำสั่งต่อไปนี้

    python application/start_game.py

    ควรจะเห็นผลลัพธ์ในเทอร์มินัลซึ่งแสดงให้เห็นว่าเริ่มเกมสำเร็จแล้ว

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

    ทดลองรันสคริปต์อีกครั้งบนเทอร์มินัล ครั้งนี้ คุณจะเห็นข้อความแสดงข้อผิดพลาดซึ่งแสดงให้เห็นว่าไม่สามารถเริ่มเกมได้ เนื่องจากคุณได้เริ่มเกมไปแล้ว ดังนั้นจึงมีแอตทริบิวต์ start_time ขึ้น ผลที่ได้ก็คือ คำขอไม่เป็นไปตามการตรวจสอบสภาวะสำหรับเอนทิตี

    คุณอาจจำได้ว่ามีความสัมพันธ์แบบกลุ่มต่อกลุ่มระหว่างเอนทิตี Game และเอนทิตี User ที่เกี่ยวข้อง และความสัมพันธ์นั้นแสดงไว้ด้วยเอนทิตี UserGameMapping

    โดยส่วนมาก คุณมักจะสืบค้นจากทั้งสองฝั่งของความสัมพันธ์ ด้วยการตั้งค่าคีย์หลัก เราสามารถค้นหาเอนทิตี User ใน Game ได้ทั้งหมด เราสามารถเปิดใช้การสืบค้นเอนทิตี Game ทั้งหมดสำหรับ User ได้โดยการใช้ดัชนีแบบอินเวิร์ท

    ใน DynamoDB ดัชนีแบบอินเวิร์ทคือดัชนีรองซึ่งเป็นการย้อนกลับของคีย์หลัก คีย์ RANGE จะกลายเป็นคีย์ HASH หรือในทางกลับกัน รูปแบบนี้จะเป็นการพลิกตารางของคุณและทำให้คุณสามารถสืบค้นอีกฝั่งนึงของความสัมพันธ์แบบกลุ่มต่อกลุ่มได้

    ในขั้นตอนดังต่อไปนี้ เราได้เพิ่มดัชนีแบบอินเวิร์ทเข้ากับตารางเพื่อแสดงวิธีใช้เพื่อดึงเอนทิตี Game ทั้งหมดสำหรับ User ที่ระบุ 

  • ขั้นตอนที่ 7: เพิ่มดัชนีแบบอินเวิร์ท

    ในขั้นตอนนี้ เราจะเพิ่มดัชนีแบบอินเวิร์ทลงในตาราง ดัชนีแบบอินเวิร์ทจะถูกสร้างขึ้นเช่นเดียวกับดัชนีรองอื่นๆ

    ในโค้ดที่คุณดาวน์โหลด สคริปต์ชื่อ add_inverted_index.py จะอยู่ในไดเร็กทอรี scripts/ สคริปต์ Python นี้จะเพิ่มดัชนีแบบอินเวิร์ทให้กับตารางของคุณ

    เนื้อหาในไฟล์ดังกล่าวมีดังต่อไปนี้

    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)
    

    ในสคริปต์นี้ เราจะเรียกใช้วิธี update_table() กับไคลเอ็นต์ DynamoDB ของเรา ในวิธีนี้ เราจะส่งรายละเอียดเกี่ยวกับดัชนีรองที่เราต้องการสร้าง ได้แก่ โครงสร้างหลักของดัชนี ปริมาณการแสดงผลที่เตรียมไว้ และแอตทริบิวต์สำหรับโปรเจ็กต์ให้กับดัชนี

    รันสคริปต์โดยการพิมพ์คำสั่งต่อไปนี้ในเทอร์มินัลของคุณ

    python scripts/add_inverted_index.py

    เทอร์มินัลจะแสดงผลลัพธ์ว่าสร้างดัชนีสำเร็จแล้ว

    Table updated successfully.

    ในขั้นตอนต่อไป เราจะใช้ดัชนีแบบอินเวิร์ทเพื่อดึงเอาเอนทิตี Game ทั้งหมดสำหรับบาง User

  • ขั้นตอนที่ 8: ดึงข้อมูลเกมให้กับผู้ใช้

    ในตอนนี้เราได้สร้างดัชนีแบบอินเวิร์ทของเราไว้แล้ว มาใช้มันเพื่อดึงเอาเอนทิตี Game ที่เล่นโดย User กันดีกว่า ในการดำเนินการนี้ เราจำเป็นต้องสืบค้นในดัชนีแบบอินเวิร์ทด้วย User ที่มีเอนทิตี Game ซึ่งเราต้องการดู

    ในโค้ดที่ดาวน์โหลด สคริปต์ชื่อ find_games_for_user.py จะอยู่ในไดเรกทอรี application/ เนื้อหาในไฟล์มีดังต่อไปนี้

    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)
    

    ในสคริปต์นี้ เราจะมีฟังก์ชันที่ชื่อว่า find_games_for_user() ซึ่งคล้ายคลึงกับฟังก์ชันที่มีในเกมของคุณ ฟังก์ชันนี้จะใช้ชื่อผู้ใช้และให้ผลเป็นเกมทั้งหมดที่เคยเล่นมาแล้วโดยผู้เล่นดังกล่าว

    คุณสามารถรันสคริปต์ในเทอร์มินัลด้วยคำสั่งต่อไปนี้

    python application/find_games_for_user.py

    สคริปต์ควรจะแสดงเกมทั้งหมดที่เล่นโดยผู้ใช้ชื่อ carrpatrick

    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>
    

    ในโมดูลนี้ เราได้เพิ่มดัชนีรองเข้าสู่ตาราง ซึ่งเป็นไปตามรูปแบบการเข้าถึงอีก 2 รูปแบบเพิ่มเติม:

    • ค้นหาเกมที่เปิดอยู่ตามแมป (อ่าน)
    • ค้นหาเกมที่เปิดอยู่ (อ่าน)

    เพื่อให้ได้มาซึ่งสิ่งนี้ เราได้ใช้ดัชนีแบบกระจายซึ่งมีเฉพาะเกมที่ยังเปิดอยู่สำหรับผู้เล่นเพิ่มเติม เราได้ใช้ทั้ง API การสืบค้น และ การสแกน กับดัชนีเพื่อค้นหาเกมที่เปิดอยู่

    นอกจากนั้น เรายังได้เห็นวิธีทำให้การเขียนขั้นสูง 2 รูปแบบเป็นไปตามแอปพลิเคชัน อันดับแรก เราใช้ธุรกรรม DynamoDB เมื่อผู้ใช้เข้าร่วมเกม ด้วยธุรกรรม เราได้จัดการกับการเขียนตามเงื่อนไขที่ซับซ้อนกับเอนทิตีหลายรายการในคำขอเดียว

    อันดับที่สอง เรานำฟังก์ชันมาใช้สำหรับผู้สร้างเกมเพื่อเริ่มเกมเมื่อพร้อม ในรูปแบบการเข้าถึงนี้ เราได้มีการอัปเดตซึ่งจำเป็นต้องมีการตรวจสอบค่าของแอตทริบิวต์ทั้ง 3 ตัวและการอัปเดตแอตทริบิวต์ทั้ง 2 ตัว คุณสามารถแสดงตรรกะที่ซับซ้อนนี้ได้ในคำขอเดียวผ่านความสามารถของการแสดงเงื่อนไขและการแสดงการอัปเดต

    สาม เราได้ทำตามรูปแบบการเข้าถึงสุดท้ายโดยการดึงข้อมูลเอนทิตี Game ทั้งหมดที่เล่นโดย User เพื่อจัดการกับรูปแบบการเข้าถึงนี้ เราได้สร้างดัชนีรองโดยใช้รูปแบบของดัชนีแบบอินเวิร์ทเพื่อให้มีการสืบค้นกับอีกด้านของความสัมพันธ์แบบกลุ่มต่อกลุ่มระหว่างเอนทิตี User และเอนทิตี Game

    ในโมดูลถัดไป เราจะล้างทรัพยากรที่เราสร้างขึ้นมา