من أكبر التعديلات التي تواجه المستخدمين الجُدد على DynamoDB و NoSQL هي كيفية نمذجة البيانات لتصفيتها عبر مجموعة بيانات كاملة. على سبيل المثال، في لعبتنا هذه، سنحتاج إلى البحث عن جلسات لعب ذات نقاط مفتوحة لنعرض للمستخدمين جلسة اللعبة التي يمكنهم الانضمام إليها.
في قاعدة بيانات ارتباطية، ستكتب بعض SQL للاستعلام عن البيانات.

SELECT * FROM games
	WHERE status = “OPEN”

بإمكان DynamoDB تصفية النتائج في عملية استعلام أو فحص، ولكن ليس بإمكانه العمل كقاعدة بيانات ارتباطية. يُطبق عامل تصفية DynamoDB بعد استعادة العناصر الأولية التي تطابق العملية استعلام أو فحص. يقلل عامل التصفية من حجم صافي الحمولة المُرسل من خدمة DynamoDB، ولكن يخضع عدد العناصر التي تم استردادها أوليًا إلى حدود حجم DynamoDB.

لحسن الحظ، هناك عدة طرق يمكنك من خلالها السماح باستعلامات تمت تصفيتها مقابل مجموعة بياناتك في DynamoDB. لتوفير عوامل تصفية فعالة بجدول DynamoDB، ستحتاج إلى تخطيط عوامل التصفية في نموذج بيانات جدولك من البداية. تذكر الدرس الذي تعلمناه في الوحدة الثانية من هذا البرنامج التدريبي: فكر في أنماط وصولك، ثم صمم جدولك.

وفي الخطوات التالية، سوف نستخدم فهرسًا ثانويًا عالميًا للبحث عن الألعاب المفتوحة. وعلى وجه التحديد، سوف نستخدم تقنية فهرس متفرق للتعامل مع نمط الوصول هذا.

الوقت اللازم لاستكمال الوحدة: 40 دقيقة


  • الخطوة 1: نموذج فهرس ثانوي متفرق

    الفهارس الثانوية عبارة عن أدوات نمذجة بيانات حيوية في DynamoDB. فهي تسمح لك بإعادة تشكيل بياناتك للسماح بأنماط استعلام بديلة. ولإنشاء فهرس ثانوي، يمكنك تحديد المفتاح الأساسي للفهرس، تمامًا مثلما قمت بإنشاء جدول سابقًا. لاحظ أن المفتاح الأساسي لفهرس ثانوي عالمي ليس لزامًا أن يكون فريدًا لكل عنصر. تقوم DynamoDB بعد ذلك بنسخ العناصر في الفهرس استنادًا إلى السمات المحددة، ويمكنك الاستعلام عنها تمامًا كما تفعل في الجدول.

    إن استخدام فهارس ثانوية متفرقة يُعتبر استراتيجية متقدمة في DynamoDB. باستخدام الفهارس الثانوية، ستقوم DynamoDB بنسخ العناصر من الجدول الأصلي فقط إذا كانت تحتوي على عناصر المفتاح الأساسي في الفهرس الثانوي. في حين أنه لن يتم نسخ العناصر التي لا تحتوي على عناصر المفتاح الأساسي، وهذا ما يجعل هذه الفهارس الثانوية تُسمى "متفرقة."

    لنرى كيف يعمل هذا الأمر في صالحنا. قد تتذكر أن لدينا نمطي وصول للبحث عن الألعاب المفتوحة:

    • البحث عن الألعاب المفتوحة (قراءة)
    • البحث عن الألعاب المفتوحة حسب الخريطة (قراءة)

    يمكننا إنشاء فهرس ثانوي باستخدام مفتاح أساسي مركب حيث يمثل مفتاح التجزئة سمة الخريطة للعبة ويمثل مفتاح النطاق سمة open_timestamp للعبة، وهو ما يشير إلى الوقت الذي تم فيه فتح اللعبة.

    يتمثل الجزء المهم بالنسبة لنا في أنه عند امتلاء اللعبة، يتم حذف السمة open_timestamp. وعند حذف السمة، تتم إزالة اللعبة المملوءة من الفهرس الثانوي لأنها لا تحتوي على قيمة لسمة مفتاح «النطاق». وهذا ما يبقي مؤشرنا متفرقًا: فهو يشمل فقط الألعاب المفتوحة التي تحتوي على السمة 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: الاستعلام عن المؤشر الثانوي المتفرق

    الآن وقد قمنا بتكوين الفهرس الثانوي، فلنستخدمه لتلبية بعض أنماط الوصول.

    لاستخدام فهرس ثانوي، لديك اثنين من استدعاءات واجهة برمجة تطبيقات متاحين هما: الاستعلام والفحص. باستخدام الاستعلام، يجب عليك تحديد مفتاح التجزئة، ومن ثم فإنه سيعود بالنتيجة المستهدفة. باستخدام الفحص، فإنك لا تحدد مفتاح التجزئة، وستعمل العملية عبر جدولك بالكامل. لا يُفضل إجراء عملياتالفحص في DynamoDB إلا في ظروف محددة نظرًا لأنها تقوم بالوصول إلى كل عنصر في قاعدة بياناتك. إذا كانت لديك كمية كبيرة من البيانات في جدول، فقد يستغرق الفحص وقتًا طويلاً للغاية. في الخطوة التالية، سنوضح لك لماذا يمكن أن تكون عمليات الفحص أداة قوية عند استخدامها مع فهارس متفرقة.

    يمكننا استخدام واجهة برمجة تطبيقات الاستعلام مقابل الفهرس الثانوي الذي قمنا بإنشائه في الخطوة السابقة للبحث عن جميع الألعاب المفتوحة حسب اسم الخريطة. يتم تقسيم الفهرس الثانوي حسب اسم الخريطة، ما يتيح لنا تقديم استعلامات مستهدفة للبحث عن الألعاب المفتوحة.

    في التعليمة البرمجية التي قمت بتنزيلها، يوجد ملف 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 للبحث عن جميع الألعاب المفتوحة للخريطة. ثم تقوم بعد ذلك بجمع الكيانات العائدة في كائنات اللعبة التي يمكن استخدامها في تطبيقك.

    قم بتنفيذ هذا البرنامج النصي عن طريق تشغيل الأمر التالي في المحطة الطرفية لديك.

    python application/find_open_games_by_map.py

    ستعرض المحطة الطرفية المخرجات التالية مع أربع ألعاب مفتوحة لخريطة 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/

    في الخطوة التالية، سوف نستخدم واجهة برمجة تطبيقات الفحص لفحص الفهرس الثانوي المتفرق.

  • الخطوة 4: فحص المؤشر الثانوي المتفرق

    في الخطوة السابقة، رأينا كيفية البحث عن ألعاب لخريطة معينة. قد يُفضل بعض اللاعبين تشغيل خريطة معينة، لذا فهذا سيكون مفيدًا. أما اللاعبين الآخرين فهم على استعداد للعب لعبة في أي خريطة. في هذا القسم، سنعرض كيفية البحث عن أي لعبة مفتوحة في التطبيق، بغض النظر عن نوع الخريطة. وللقيام بذلك، سنستخدم واجهة برمجة تطبيقات الفحص.

    بشكل عام، أنت لا ترغب في تصميم جدولك لاستخدام عملية فحص 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)

    هذه التعليمة البرمجية تشبه التعليمة البرمجية الموجودة في الخطوة السابقة. ومع ذلك، فبدلاً من استخدام طريقة query() في عميل DynamoDB، فإننا نستخدم طريقة scan(). ولأننا نستخدم scan()، فإننا لا نحتاج لتحديد أي شيء عن الشروط الأساسية مثلما فعلنا مع query(). نحن فقط لدينا DynamoDB ترجع مجموعة من العناصر في أي ترتيب محدد.

    يمكنك تشغيل البرنامج النصي باستخدام الأمر التالي في المحطة الطرفية لديك.

    python application/find_open_games.py

    يجب أن تطبع محطتك الطرفية قائمة تحتوي على تسع ألعاب مفتوحة عبر مجموعة متنوعة من الخرائط.

    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>
    

    في هذه الخطوة، رأينا كيف أن استخدام عملية الفحصقد يكون الاختيار المناسب في هذه الظروف المحددة. لقد استخدمنا الفحص لجذب مجموعة متنوعة من الكيانات من فهرسنا الثانوي المتفرق لإظهار الألعاب المفتوحة للاعبين.

    في الخطوات التالية، سنلبي نمطين للوصول:

    • انضمام مستخدم إلى لعبة (كتابة)
    • بدء اللعبة (كتابة)

    لتلبية نمط الوصول "انضمام مستخدم إلى لعبة" في الخطوات التالية، سنستخدم معاملات DynamoDB. يشيع استخدام المعاملات في الأنظمة الارتباطية للعمليات التي تؤثر على عناصر بيانات متعددة في نفس الوقت. على سبيل المثال، تخيل أنك تدير بنكًا. وتقوم إحدى العميلات واسمها، أليخاندرا، بتحويل 100 USD إلى حساب عميلة أخرى، اسمها آنا. عند تسجيل هذه المعاملة، يمكنك استخدام معاملة للتأكد من تطبيق التغييرات على أرصدة كلتا العميلتين وليس عميلة واحدة فقط.

    تسهل معاملات DynamoDB إنشاء التطبيقات التي تغيّر عناصر متعددة كجزء من عملية واحدة. باستخدام المعاملات، يمكنك العمل على ما يصل إلى 10 عناصر كجزء من طلب معاملة واحدة.

    في استدعاء واجهة برمجة تطبيقات TransactWriteItem، يمكنك استخدام العمليات التالية:

    • وضع: لإدخال عنصر أو الكتابة فوقه.
    • تحديث: لتحديث عنصر موجود.
    • حذف: لإزالة عنصر.
    • التحقق من الحالة: لتأكيد حالة بشأن عنصر موجود دون تغيير العنصر.

     

    في الخطوة التالية، سوف نستخدم معاملة DynamoDB عند إضافة مستخدمين جدد إلى لعبة مع عدم الإفراط في تعبئة اللعبة.

  • الخطوة 5: إضافة مستخدمين إلى لعبة

    يتمثل نمط الوصول الأول الذي سنتناوله في هذه الوحدة في إضافة مستخدمين جدد إلى لعبة.

    وعند إضافة مستخدم جديد إلى لعبة، سنحتاج إلى:

    • التأكد من عدم وجود 50 لاعبًا بالفعل في اللعبة (يمكن أن تضم كل لعبة 50 لاعبًا بحد أقصى).
    • التأكد من أن المستخدم ليس موجودًا بالفعل في اللعبة.
    • إنشاء كيان UserGameMapping جديد لإضافة المستخدم إلى اللعبة.
    • زيادة سمة الأشخاص في كيان اللعبة لتتبع عدد اللاعبين المشاركين في اللعبة.

    لاحظ أن إنجاز كل هذه الأمور يتطلب إجراءات كتابة عبر كيان اللعبة الموجود وكيان 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() بإجراء معاملة كتابة. وتحتوي المعاملة على عمليتين.

    في العملية الأولى للمعاملة، سنستخدم عملية Put (وضع) لإدراج كيان UserGameMapping جديد. وكجزء من تلك العملية، سنقوم بتحديد شرط أنه يجب ألا توجد سمة SK لهذا الكيان. فهذا من شأنه ضمان عدم وجود أي كيان يحتوي على PK وSK بالفعل. وإذا كان هذا الكيان موجودًا بالفعل، فذلك يعني أن هذا المستخدم قد انضم إلى اللعبة بالفعل.

    تتمثل العملية الثانية في إجراء عملية «تحديث» على كيان اللعبة لزيادة سمة الأشخاص بمقدار شخص واحد. وكجزء من هذه العملية، سنضيف فحصًا مشروطًا يتمثل في أن القيمة الحالية للأشخاص ليست أكبر من 50. وبمجرد انضمام 50 شخصًا إلى اللعبة، تكون اللعبة كاملة وجاهزة للبدء.

    يمكنك تشغيل هذا البرنامج النصي باستخدام الأمر التالي في المحطة الطرفية لديك.

    python application/join_game.py

    يجب أن تشير المخرجات في المحطة الطرفية لديك إلى أنه قد تمت إضافة المستخدم إلى اللعبة.

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

    لاحظ أنه إذا حاولت تشغيل البرنامج النصي مرة أخرى، فلن تتمكن الدالة من ذلك. لقد تمت إضافة المستخدم vlopez إلى اللعبة بالفعل، لذا فإن محاولة إضافة المستخدم مرة أخرى لا يتفق مع الشروط التي حددناها.

    إن إضافة معاملات DynamoDB يسهّل بشكل كبير سير العمل حول العمليات المعقدة مثل هذه. فبدون معاملات، قد يتطلب ذلك إجراء عدة استدعاءات لواجهة برمجة التطبيقات بشروط معقدة واستعادة يدوية في حالة حدوث تعارضات. والآن، يمكننا تنفيذ مثل هذه العمليات المعقدة في أقل من 50 سطرًا من التعليمات البرمجية.

    في الخطوة التالية، سنتناول نمط الوصول "بدء اللعبة (كتابة)".

  • الخطوة 6: بدء لعبة

    فبمجرد أن تضم اللعبة 50 مستخدمًا، يمكن لمنشئ اللعبة بدء تشغيلها لبدء اللعب. في هذه الخطوة، سنعرض كيفية التعامل مع نمط الوصول هذا.

    عندما تتلقى الواجهة الخلفية للتطبيق طلبًا لبدء اللعبة، فإننا نتحقق من ثلاثة أشياء:

    • احتواء اللعبة على 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، وتشغِّل طلبًا لتحديث كيان اللعبة لبدء اللعبة.

    تحدد المعلمة ConditionExpression الموجودة في استدعاء update_item() كل فحص من عمليات الفحص الثلاثة التي ذكرناها سابقًا في هذه الخطوة - احتواء اللعبة على 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 . ونتيجة لذلك، سيفشل الطلب في إجراء الفحص المشروط على الكيان.

    قد تتذكر أن هناك علاقة متعدد إلى متعدد بين كيان اللعبة وكيانات المستخدم المرتبطة، ويتم تمثيل العلاقة بواسطة كيان UserGameMapping.

    وفي كثير من الأحيان، قد ترغب في الاستعلام عن جانبي علاقة ما. ومن خلال إعداد المفتاح الأساسي الخاص بنا، يمكننا البحث عن جميع كيانات المستخدم في لعبة ما. يمكننا أن نمكِّن المستخدم من الاستعلام عن جميع كيانات اللعبة باستخدام فهرس معكوس.

    ففي DynamoDB، يُعتبر الفهرس المقلوب فهرسًا ثانويًا عكس المفتاح الأساسي الخاص بك. فيصبح مفتاح RANGE (النطاق) مفتاح HASH (التجزئة) والعكس صحيح. يقلب هذا النمط جدولك ويسمح لك بالاستعلام على الجانب الآخر من علاقات متعدد إلى متعدد الخاصة بك.

    في الخطوات التالية، سنضيف فهرسًا معكوسًا إلى الجدول وسنعرض كيفية استخدامه لاسترداد جميع كيانات اللعبة لمستخدم معين. 

  • الخطوة 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.

    في الخطوة التالية، سوف نستخدم فهرسنا المعكوس لاسترداد جميع كيانات اللعبة لأجل مستخدم معين.

  • الخطوة 8: استرداد الألعاب للمستخدم

    الآن وقد أنشأنا فهرسنا المعكوس، لنستخدمه لاسترداد كيانات اللعبة التي لعبها مستخدم. للتعامل مع هذا الأمر، سنحتاج إلى الاستعلام عن الفهرس المعكوس باستخدام المستخدم الذي نريد رؤية كيانات اللعبة الخاصة به.

    في التعليمة البرمجية التي قمت بتنزيلها، يوجد برنامج نصي 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>
    

    في هذه الوحدة، أضفنا فهرسًا ثانويًا إلى الجدول. وهذا لبى نمطي وصول إضافيين هما:

    • البحث عن الألعاب المفتوحة حسب الخريطة (قراءة)
    • البحث عن الألعاب المفتوحة (قراءة)

    ولتحقيق ذلك، استخدمنا فهرسًا متفرقًا شمل فقط الألعاب التي كانت لا تزال مفتوحة للاعبين إضافيين. وقد استخدمنا بعد ذلك واجهة برمجة تطبيقات الاستعلام و الفحص مقابل الفهرس للبحث عن الألعاب المفتوحة.

    أيضًا، رأينا كيفية تلبية عمليتي كتابة متقدمتين في التطبيق. أولاً، استخدمنا معاملات DynamoDB عند انضمام مستخدم إلى لعبة. ومن خلال المعاملات، تعاملنا مع الكتابة الشرطية المعقدة عبر كيانات متعددة في طلب واحد.

    ثانيًا، قمنا بتطبيق دالة لـ «مُنشئ لعبة» لبدء اللعبة حينما تكون جاهزة. في نمط الوصول هذا، كانت لدينا عملية تحديث تطلبت التحقق من قيمة ثلاث سمات وتحديث سمتين. ويمكنك التعبير عن هذا المنطق المعقد في طلب واحد من خلال قوة تعبيرات الشرط وتحديث التعبيرات.

    ثالثًا، لبينا نمط الوصول النهائي من خلال استرداد جميع كيانات اللعبة التي لعبها المستخدم. وللتعامل مع نمط الوصول هذا، أنشأنا فهرسًا ثانويًا باستخدام نمط الفهرس المعكوس للسماح بالاستعلام على الجانب الآخر من العلاقة متعدد إلى متعدد بين كيانات المستخدم وكيانات اللعبة.

    في الوحدة التالية، سنقوم بتنظيف الموارد التي أنشأناها.