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

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


عند تصميم المفتاح الأساسي لجدول DynamoDB، ضع في اعتبارك أفضل الممارسات التالية:

  • ابدأ بالكيانات المختلفة في جدولك. فإذا كنت تخزِّن عدة أنواع مختلفة من البيانات في جدول واحد — على سبيل المثال الموظفين والإدارات والعملاء والأوامر— فتأكد من أن المفتاح الأساسي يتضمن طريقة لتحديد كل كيان بشكل واضح وتمكين الإجراءات الأساسية بشأن العناصر الفردية.
  • استخدم البادئات للتمييز بين أنواع الكيانات. إن استخدام البادئات للتمييز بين أنواع الكيانات يمكن أن يمنع حدوث تصادمات ويساعد في الاستعلام. على سبيل المثال، إذا كان لديك عملاء وموظفين في نفس الجدول، فيمكن أن يكون المفتاح الأساسي للعميل CUSTOMER#<CUSTOMERID>، ويمكن أن يكون المفتاح الأساسي للموظف EMPLOYEE#<EMPLOYEEID>.
  • ركز أولاً على إجراءات عنصر واحد، ثم أضف إجراءات عنصر متعدد إذا أمكن ذلك. بالنسبة للمفتاح الأساسي، من المهم أن تتمكن من تلبية خيارات القراءة والكتابة على عنصر واحد باستخدام واجهات برمجة التطبيقات لعنصر واحد: GetItem وPutItem وUpdateItem وDeleteItem. باستخدام المفتاح الأساسي، قد تتمكن أيضًا من تلبية أنماط القراءة متعددة العناصر باستخدام الاستعلام. إذا لم يكن كذلك، يمكنك إضافة فهرس ثانوي للتعامل مع حالات استخدام الاستعلام.

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


  • الخطوة 1. تصميم المفتاح الأساسي

    دعونا ننظر في الكيانات المختلفة، كما هو مقترح في المقدمة السابقة. في اللعبة، لدينا الكيانات التالية:

    • المستخدم
    • اللعبة
    • UserGameMapping

    تُعد UserGameMapping سجلاً يشير إلى انضمام المستخدم إلى اللعبة. وهناك علاقة متعدد إلى متعدد بين المستخدم واللعبة.

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

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

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

    ومع وضع ذلك في الاعتبار، دعنا نستخدم النمط التالي لقيم التجزئة والنطاق لكل نوع كيان.

    الكيان التجزئة النطاق
    المستخدم USER#<USERNAME> #METADATA#<USERNAME>
    اللعبة GAME#<GAME_ID> #METADATA#<GAME_ID>
    UserGameMapping GAME#<GAME_ID> USER#<USERNAME>

    دعونا نستعرض الجدول السابق.

    بالنسبة لكيان المستخدم ، فإن قيمة التجزئة تمثل USER#<USERNAME>. لاحظ أننا نستخدم بادئة لتحديد الكيان ومنع أي تصادم محتمل عبر أنواع الكيانات.

    أما بالنسبة لقيمة النطاق الموجودة بشأن كيان المستخدم ، فإننا نستخدم بادئة ثابتة هي #METADATA# متبوعة بقيمة USERNAME. بالنسبة لقيمة النطاق، من المهم أن يكون لدينا قيمة معروفة، مثل USERNAME. وهذا يسمح باتخاذ إجراءات عنصر واحد مثل GetItem وPutItem وDeleteItem.

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

    يحتوي كيان اللعبة على تصميم مفتاح أساسي يشبه تصميم كيان المستخدم. فهو يستخدم بادئة مختلفة (GAME#) وGAME_ID بدلا من USERNAME، ولكن تنطبق نفس المبادئ.

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

    في الخطوة التالية، سننشئ جدولاً يتضمن تصميم المفتاح الأساسي هذا. 

  • الخطوة 2: إنشاء جدول

    الآن وقد صممنا المفتاح الأساسي، لنقم بإنشاء جدول.

    تتضمن التعليمات البرمجية التي قمت بتنزيلها في الخطوة 3 بالوحدة 1 برنامج Python النصي في الدليل scripts/ الذي يحمل اسم create_table.py. وفيما يلي محتويات برنامج Python النصي.

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.create_table(
            TableName='battle-royale',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            KeySchema=[
                {
                    "AttributeName": "PK",
                    "KeyType": "HASH"
                },
                {
                    "AttributeName": "SK",
                    "KeyType": "RANGE"
                }
            ],
            ProvisionedThroughput={
                "ReadCapacityUnits": 1,
                "WriteCapacityUnits": 1
            }
        )
        print("Table created successfully.")
    except Exception as e:
        print("Could not create table. Error:")
        print(e)
    

    يستخدم البرنامج النصي السابق عملية إنشاء جدول باستخدام Boto 3، وهو AWS SDK for Python. وتعلن العملية عن تعريفين للسمة، وهما من السمات المكتوبة لاستخدامهما في المفتاح الأساسي. ورغم أن DynamoDB تُعد بلا مخطط، إلا أن عليك إعلان أسماء السمات المستخدمة للمفاتيح الأساسية وأنواعها. ويجب تضمين السمات في كل عنصر مكتوب بالجدول، وبالتالي لابد من تحديدها أثناء قيامك بإنشاء جدول.

    ونظرًا لأننا نخزِّن كيانات مختلفة في جدول واحد، فليس بإمكاننا استخدام أسماء سمات المفتاح الأساسي مثل UserId. تعني السمة شيئًا مختلفًا استنادًا إلى نوع الكيان الذي يتم تخزينه. على سبيل المثال، قد يكون المفتاح الأساسي لأحد المستخدمين هو USERNAME، وقد يكون المفتاح الأساسي للعبة هو GAMEID. وبناء على ذلك، فإننا نستخدم أسماءً عامة للسمات، مثل PK (لمفتاح التقسيم) وSK (لمفتاح الفرز).

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

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

    لإنشاء الجدول، قم بتشغيل برنامج Python النصي باستخدام الأمر التالي.

    python scripts/create_table.py

    يجب أن يعرض البرنامج النصي هذه الرسالة: "تم إنشاء الجدول بنجاح."

    في الخطوة التالية، سنقوم بتحميل مجمع لبعض بيانات الأمثلة في الجدول. 

  • الخطوة 3: التحميل المجمع للبيانات في الجدول

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

    في الدليل scripts/، ستجد ملفًا يُسمى items.json. يحتوي هذا الملف على 835 مثال لعنصر تم إنشاؤه عشوائيًا لهذا التمرين المعملي. وتشمل هذه العناصر كيانات Userو Game و UserGameMapping. افتح الملف إذا كنت تريد رؤية بعض الأمثلة لهذه العناصر.

    كما يحتوي المجلد scripts/ أيضًا على ملف يسمى bulk_load_table.py يقرأ العناصر الموجودة في الملف items.json ويكتبها بصورة مجمعة في الجدول DynamoDB. وفيما يلي محتويات الملف.

    import json
    
    import boto3
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('battle-royale')
    
    items = []
    
    with open('scripts/items.json', 'r') as f:
        for row in f:
            items.append(json.loads(row))
    
    with table.batch_writer() as batch:
        for item in items:
            batch.put_item(Item=item)
    

    في هذا البرنامج النصي، بدلاً من استخدام العميل ذي المستوى المنخفض في Boto 3، فإننا سنستخدم كائن مواردذا مستوى أعلى. توفر كائنات الموارد واجهة أسهل لاستخدام واجهة برمجة تطبيقات AWS. ويُعد كائن الموارد مفيدًا في هذا الموقف نظرًا لأنه ينظم طلباتنا في دفعات. تقبل عملية BatchWriteItem أكثر من 25 عنصرًا في طلب واحد. ويعالج كائن الموارد هذه الدفعات لنا بدلاً من قيامنا بتقسيم بياناتنا إلى طلبات مؤلفة من 25 عنصرًا أو أقل.

    قم بتشغيل البرنامج النصي bulk_load_table.py وحمِّل جدولك بالبيانات من خلال تشغيل الأمر التالي في المحطة الطرفية.

    python scripts/bulk_load_table.py

    يمكنك التأكد من أنه تم تحميل جميع بياناتك في الجدول بتشغيل عملية فحص والحصول على العدد.

    aws dynamodb scan \
     --table-name battle-royale \
     --select COUNT

    من المفترض أن يعرض ذلك النتائج التالية.

    {
        "Count": 835, 
        "ScannedCount": 835, 
        "ConsumedCapacity": null
    }
    

    يجب أن ترى عددًا يبلغ 835، ما يشير إلى أنه تم تحميل جميع عناصرك بنجاح.

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

  • الخطوة 4: استرداد أنواع متعددة من الكيانات في طلب واحد

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

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

    يتضمن هذا الطلب نوعين من الكيانات: كيان اللعبة وكيان UserGameMapping. ومع ذلك، فهذا لا يعني أننا سنحتاج إلى تقديم طلبات متعددة.

    في التعليمات البرمجية التي قمت بتنزيلها، يوجد برنامج نصي fetch_game_and_players.py في الدليل application/. يوضح هذا البرنامج النصي كيف يمكنك هيكلة التعليمات البرمجية الخاصة بك لاسترداد كيان اللعبة وكيان UserGameMapping للعبة في طلب واحد.

    تتألف التعليمة البرمجية التالية من البرنامج النصي fetch_game_and_players.py.

    import boto3
    
    from entities import Game, UserGameMapping
    
    dynamodb = boto3.client('dynamodb')
    
    GAME_ID = "3d4285f0-e52b-401a-a59b-112b38c4a26b"
    
    
    def fetch_game_and_users(game_id):
        resp = dynamodb.query(
            TableName='battle-royale',
            KeyConditionExpression="PK = :pk AND SK BETWEEN :metadata AND :users",
            ExpressionAttributeValues={
                ":pk": { "S": "GAME#{}".format(game_id) },
                ":metadata": { "S": "#METADATA#{}".format(game_id) },
                ":users": { "S": "USER$" },
            },
            ScanIndexForward=True
        )
    
        game = Game(resp['Items'][0])
        game.users = [UserGameMapping(item) for item in resp['Items'][1:]]
    
        return game
    
    
    game = fetch_game_and_users(GAME_ID)
    
    print(game)
    for user in game.users:
        print(user)
    

    في بداية هذا البرنامج النصي، سنستورد مكتبة Boto 3 وبعض الفئات البسيطة لتمثيل الكائنات في التعليمة البرمجية الخاصة بتطبيقنا. يمكنك رؤية التعريفات الخاصة بهذه الكيانات في الملف application/entities.py.

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

    تقوم الوظيفة fetch_game_and_users بالقليل من الأعمال. أولاً، تقدم طلب استعلام إلى DynamoDB. ويستخدم هذا الاستعلام مفتاح PK يمثل GAME#<GameId>. بعد ذلك، تطلب أي كيانات عندما يكون مفتاح الفرز بين #METADATA#<GameId> و USER$. وهذا يجلب كيان اللعبة ، الذي يكون مفتاح الفرز الخاص به هو #METADATA#<GameId>، وجميع كيانات UserGameMappings، التي تبدأ مفاتيحها بـ USER#. تُصنف مفاتيح الفرز من نوع السلسلة حسب التعليمات البرمجية لأحرف ASCII. وتأتي علامة الدولار ($) مباشرة بعد علامة الجنيه (#) في ASCII، وهذا سيضمن حصولنا على جميع التعيينات الموجودة في كيان UserGameMapping.

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

    توضح نهاية البرنامج النصي استخدام الوظيفة وتطبع الكائنات الناتجة. يمكنك تشغيل البرنامج النصي في وحدتك الطرفية باستخدام الأمر التالي.

    python application/fetch_game_and_players.py

    يجب أن يقوم البرنامج النصي بطباعة كائن «اللعبة» وجميع كائنات UserGameMapping على وحدة التحكم.

    Game<3d4285f0-e52b-401a-a59b-112b38c4a26b -- Green Grasslands>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- branchmichael>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- deanmcclure>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- emccoy>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- emma83>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- iherrera>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- jeremyjohnson>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- lisabaker>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- maryharris>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- mayrebecca>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- meghanhernandez>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- nruiz>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- pboyd>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- richardbowman>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- roberthill>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- robertwood>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- victoriapatrick>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- waltervargas>
    

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


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

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

    • إنشاء ملف تعريف المستخدم (كتابة)
    • تحديث ملف تعريف المستخدم (كتابة)
    • الحصول على ملف تعريف المستخدم (قراءة)
    • إنشاء لعبة (كتابة)
    • عرض لعبة (قراءة)
    • ضم لعبة إلى مستخدم (كتابة)
    • بدء اللعبة (كتابة)
    • تحديث لعبة لمستخدم (كتابة)
    • حديث لعبة (كتابة)

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