หนึ่งในการปรับเปลี่ยนที่ใหญ่ที่สุดสำหรับผู้ใช้ที่ยังใหม่สำหรับ 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
ในโมดูลถัดไป เราจะล้างทรัพยากรที่เราสร้างขึ้นมา