Önceki modülde oyun uygulamasının erişim modellerini tanımladık. Bu modülde DynamoDB tablosu için birincil anahtar tasarlayacağız ve temel erişim modellerini etkinleştireceğiz.

Modülü Tamamlama Süresi: 20 Dakika


DynamoDB tablosu için birincil anahtarı tasarlarken aşağıdaki en iyi uygulamaları aklınızda bulundurun:

  • Tablonuzdaki varlıklar ile başlayın. Tek bir tabloda çalışanlar, bölümler, müşteriler ve siparişler gibi birden fazla farklı türde veri depoluyorsanız birincil anahtarınızın her bir varlığı açıkça tanımlayabilmesi ve bireysel nesnelerde temel eylemler etkinleştirebilmesinin bir yolu olduğundan emin olun.
  • Varlık türlerini birbirinden ayırmak için ön ekler kullanın. Varlık türlerini birbirinden ayırmak için ön ekler kullanmak çarpışmaları önleyebilir ve sorgulamada yardımcı olabilir. Örneğin aynı tabloda hem müşteriler hem çalışanlar varsa müşteri için birincil anahtar CUSTOMER#<CUSTOMERID> (MÜŞTERİ#<MÜŞTERİKİMLİĞİ>) olabilir ve bir çalışan için birincil anahtar EMPLOYEE#<EMPLOYEEID> (ÇALIŞAN#<ÇALIŞANKİMLİĞİ>) olabilir.
  • Önce tek öğe eylemlere yoğunlaşın ve sonra mümkünse çoklu nesne eylemleri ekleyin. Bir birincil anahtar için tek nesne API'leri kullanarak tek bir öğede okuma ve yazma seçeneklerini karşılamanız önemlidir: GetItem (ÖğeAl), PutItem (ÖğeKoy), UpdateItem (ÖğeGüncelle) ve DeleteItem (ÖğeeSil). Ayrıca Sorgu kullanarak birincil anahtar ile çoklu nesne okuma modellerini karşılayabilirsiniz. İşe yaramazsa, Sorgu kullanım örnekleri ile başa çıkması için bir ikincil dizin ekleyebilirsiniz.

Bu en iyi uygulamaları aklımızda bulundurarak oyun uygulamasının tablosu için birincil anahtar tasarlayalım ve bazı temel eylemleri gerçekleştirelim.


  • 1. Adım. Birincil anahtar tasarlama

    Girişte önerildiği üzere farklı varlıkları göz önünde bulunduralım. Oyunda aşağıdaki varlıklar mevcuttur:

    • Kullanıcı
    • Oyun
    • UserGameMapping (KullanıcıOyunEşleme)

    Bir UserGameMapping (KullanıcıOyunEşleme) bir kullanıcının oyuna katıldığını belirten bir kayıttır. Bu nedenle,Kullanıcı ve Oyun arasında çoka çok bir ilişki vardır.

    Çoka çok eşleştirme genellikle aynı anda iki Sorgu modelini karşılamayı isteyeceğinizin belirtisidir ve bu oyun bir istisna değildir. Bir oyuna katılan tüm kullanıcıları bulması gereken bir erişim modelinin yanı sıra bir kullanıcının oynadığı tüm oyunları bulması gereken başka bir modelimiz var.

    Veri modelinizde aralarında ilişki olan birden fazla varlık varsa genellikle hem KARMA hem ARALIK değerleri ile birleşik birincil anahtar kullanırsınız. Birleşik birincil anahtar, bize ihtiyacımız olan sorgu modellerinden birini karşılamak için KARMA anahtarında Sorgu becerisi verir. DynamoDB belgelerinde bölüm anahtarına KARMA denir ve sıralama anahtarına ARALIK denir ve bu rehberde özellikle kod veya DynamoDB JSON kablo protokolü formatı hakkında konuştuğumuzda API terminolojisini değiştirilebilir şekilde kullanırız.

    Diğer iki veri varlığı—Kullanıcı ve Oyun ARALIK değeri için doğal bir özelliğe sahip değildir çünkü Kullanıcı veya Oyundaki erişim modelleri bir anahtar-değer aramasıdır. Bir ARALIK değeri gerektiğinden ARALIK anahtarı için bir dolgu değer sağlayabiliriz.

    Bunu aklımızda tutarak her varlık türü için KARMA ve ARALIK değerleri için aşağıdaki modeli kullanalım.

    Varlık KARMA ARALIK
    Kullanıcı USER#<USERNAME> (KULLANICI#<KULLANICIADI>) #METADATA#<USERNAME> (#METAVERİ#<KULLANICIADI>)
    Oyun GAME#<GAME_ID> (OYUN#<OYUN_KİMLİĞİ>) #METADATA#<GAME_ID> (#METAVERİ#<OYUN_KİMLİĞİ>)
    UserGameMapping (KullanıcıOyunEşleme) GAME#<GAME_ID> (OYUN#<OYUN_KİMLİĞİ>) USER#<USERNAME> (KULLANICI#<KULLANICIADI>)

    Önceki tablonun üzerinden geçelim.

    Kullanıcı varlığı için KARMA değeri USER#<USERNAME> (KULLANICI#<KULLANICIADI>). Varlığı tanımlamak ve varlık türleri arasındaki muhtemel çarpışmaları tanımlamak için ön ek kullandığımızı göz önünde bulundurun.

    Kullanıcı varlığındaki ARALIK değeri için #METADATA# (#METAVERİ#) statik ön ekini ve arkasından USERNAME (KULLANICIADI) değerini kullanıyoruz. ARALIK değeri için USERNAME (KULLANICI ADI) gibi bilinen bir değerimiz olması önemlidir. Bu, GetItem (ÖğeAl), PutItem (ÖğeKoy), ve DeleteItem (ÖğeSil) gibi tek öğe eylemleri sağlar.

    Ancak, bir dizin için bu sütunu KARMA anahtarı olarak kullanırsak eşit bölme sağlamak için farklı Kullanıcı varlıkları boyunca farklı değerli ARALIK değeri isteriz. Bu nedenle USERNAME (KULLANICI ADI) ekleriz.

    Oyun varlığı Kullanıcı varlığının tasarımına benzer bir birincil anahtar varlığına sahiptir. USERNAME (KULLANICI ADI) yerine farklı bir ön ek (GAME# (OYUN#)) ve bir GAME_ID (OYUN_KİMLİĞİ) kullanır.

    Son olarak UserGameMapping (KullanıcıOyunEşleme) Oyun varlığı ile aynı KARMA anahtarı kullanır. Bu tek bir sorguda sadece Oyun için meta veri getirmemizi değil aynı zamanda bir Oyundaki tüm kullanıcıları getirmemizi sağlar. Sonra hangi oyuncunun belirli oyuna katıldığını belirlemek için UserGameMapping (KullanıcıOyunEşleme) üzerindeki ARALIK için Kullanıcı varlığını kullanırız.

    Sonraki adımda bu birincil anahtar tasarımı ile bir tablo oluşturacağız. 

  • Adım 2: Bir tablo oluştur

    Birincil anahtarı tasarladığımıza göre bir tablo oluşturalım.

    Modül 1’in Adım 3’ünde indirdiğiniz kod scripts/ dizininde create_table.py isimli bir Python betiği içerir. Python betiğinin içerikleri aşağıdaki gibidir:

    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)
    

    Önceki betik AWS SDK for Python olan Boto 3’ü kullanarak CreateTable (TabloOluştur) işlemini kullanır. İşlem, birincil anahtarda kullanılacak türü belirtilen özellikler olan iki özellik tanımı açıklar. DynamoDB şemasız olmasına rağmen birincil anahtarlar için kullanılan özelliklerin isimlerini ve türlerini açıklamalısınız. Özellikler tabloya yazılan her nesneye dâhil edilmelidir ve bu yüzden tablo oluştururken belirtilmelidir.

    Tek bir tabloda farklı varlıklar depoladığımız için UserId (KullanıcıKimliği) gibi birincil anahtar özellik isimleri kullanamayız. Depolanan varlık türüne bağlı olarak bu özellik farklı anlamlara gelebilir. Örneğin, bir kullanıcı için birincil anahtar USERNAME (KULLANICI ADI) iken bir oyun için birincil anahtar GAMEID (OYUNKİMLİĞİ) olabilir. Buna göre PK (bölme anahtarı için) ve SK (sıralama anahtarı için) gibi özellikler için genel isimler kullanırız.

    Anahtar şemada özellikleri yapılandırdıktan sonra tablo için tedarik edilen aktarım hızı belirtiriz. DynamoDB’nin iki kapasite modu vardır: tedarik edilen ve isteğe bağlı. Tedarik edilen kapasite modunda tam olarak istediğiniz okuma ve yazma aktarım hızını belirtirsiniz. Kullansanız da kullanmasanız da bu kapasite için ödeme yaparsınız.

    DynomoDB isteğe bağlı kapasite modunda istek başına ödeme yapabilisiniz. Tedarik edilen aktarım hızını tamamen kullandığınızda istek üzerinenin masrafı daha yüksek olmasına rağmen kapasite planlamasıyla veya kısıtlanma endişesiyle vakit kaybetmek zorunda kalmazsınız. İsteğe bağlı mod ani artışlar olan ve tahmin edilemeyen iş yüklerinde çok iyi çalışır. DynamoDB ücretsiz kullanım kategorisine girdiği için olduğu için bu laboratuvarda tedarik edilen kapasite modunu kullanıyoruz.

    Tablo oluşturmak için Python betiğini aşağıdaki komut ile yürütün.

    python scripts/create_table.py

    Betik şu mesajı vermelidir: “Tablo başarıyla oluşturuldu.”

    Sonraki adımda bazı örnek verileri tabloya toplu yükleyeceğiz. 

  • Adım 3: Tabloya toplu veri yükleme

    Bu adımda önceki adımda oluşturduğumuz DynamoDB’ye bazı verileri toplu yükleyeceğiz. Bu da ileriki adımlarda kullanacağımız bir örnek verimiz olacağı anlamına geliyor.

    scripts/ dizininde items.json adında bir dosya bulacaksınız. Bu dosya, bu laboratuvar için rastgele oluşturulmuş 835 örnek öğe içerir. bu nesneler Kullanıcı, Oyun ve UserGameMapping(KullanıcıOyunEşleme) varlıklarını içerir. Örneklerin bazılarını görmek istiyorsanız dosyayı açın.

    Ayrıca scripts/ dizininde items.json dosyasındaki öğeleri okuyan ve bunları DynamoDB tablosuna toplu yazan bulk_load_table.py adında bir dosya vardır. Dosya içerikleri aşağıdaki gibidir.

    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)
    

    Bu betikte Boto 3’de düşük seviye istemci kullanmak yerine yüksek seviye bir Kaynak nesnesi kullanacağız. Kaynak nesneleri AWS API'leri kullanmak için daha kolay arabirim sağlar. Kaynak nesnesi bu durumda kullanışlıdır çünkü isteklerimizi grup haline getirir. BatchWriteItem (ÖğeyiGrupHalindeYaz) işlemi tek bir istekte 25’e kadar öğe kabul eder. Verilerimizi 25 veya daha az nesneler halinde ayırmak zorunda bırakmak yerine Kaynak nesnesi grup haline getirme işlemini bizim yerimize yapar.

    bulk_load_table.py betiğini yürütün ve aşağıdaki komutu yürüterek tablonuza veri yükleyin.

    python scripts/bulk_load_table.py

    Bir Tarama işlemi yürüterek ve sayı elde ederek tüm verilerinizin tabloya yüklendiğinden emin olabilirsiniz.

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

    Bu aşağıdaki sonuçları göstermelidir.

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

    835 dosya sayısı görmelisiniz bu da tüm öğelerinizin başarıyla yüklendiğini gösterir.

    Sonraki adımda tek bir istekte çoklu varlık türlerini alabileceğinizi göstereceğiz bu da uygulamanızda yaptığınız toplam ağ isteklerini düşürür ve uygulama performansınızı arttırır.

  • Adım 4: Tek bir istekte çoklu varlık türleri alma

    Önceki modülde söylediğimiz gibi aldığı istek sayısı için DynamoDB’nizi optimize etmelisiniz. Ayrıca DynamoDB’nin ilişkisel bir veri tabanının sahip olduğu birleşimlere sahip olmadığını söyledik. Bunun yerine isteklerinizde birleşim benzeri davranışlar sağlaması için tablonuzu tasarlarsınız.

    Bu adımda tek bir istekte çoklu varlık türleri alacağız. Bu oyunda bir oyun oturumu hakkında ayrıntılar getirmek isteyebiliriz. Bu ayrıntılar oyunun başladığı zaman, bittiği zaman, kiminin yerleştirildiği ve oyunu oynayan kullanıcılar hakkında ayrıntılar gibi bilgileri içerir.

    Bu istek iki varlık türüne hitap eder: Oyun varlığı ve UserGameMapping (KullanıcıOyunEşleme) varlığı. Ancak bu birden fazla istek yapmamız gerektiği anlamına gelmez.

    İndirdiğiniz kodda fetch_game_and_players.py betiği application/ dizinindedir. Bu betik tek bir istekte oyun için hem Oyun varlığı ve UserGameMapping (KullanıcıOyunEşleme) varlığı alabilmek için kodunuzu nasıl yapılandıracağınızı gösterir.

    Aşağıdaki kod fetch_game_and_players.py betiğini oluşturur.

    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)
    

    Bu betiğin başında uygulama kodumuzdaki nesneleri temsil etmesi için Boto 3 kütüphanesini ve bazı sınıfları içe aktarıyoruz. application/entities.py dosyasında bu varlıkların tanımlarını görebilirsiniz.

    Betiğin gerçek görevi modülde tanımlanan fetch_game_and_users işlevinde gerçekleşir. Bu, bu veriye ihtiyaç duyan herhangi bir uç nokta tarafından kullanılması için uygulamanızda tanımlayacağınız bir işleve benzer.

    fetch_game_and_users işlevi birkaç işe yarar. Öncelikle, DynamoDB’ye bir Sorgu isteği yapar. Bu Sorgu, GAME#<GameId> (OYUN#<OyunKimliği>) PK’sı kullanır. Sonra, sıralama anahtarı #METADATA#<GameId> (#METAVERİ#<OyunKimliği>) ve USER$ (KULLANICI$) arasında olan varlıklar için istekte bulunur. Bu, sıralama anahtarı #METADATA#<GameId> (#METAVERİ#<OyunKimliği>) olan Oyun varlığını, anahtarları USER# (KULLANICI#) ile başlayan tüm UserGameMappings (KullanıcıOyunEşlemeleri) varlıklarını edinir. Dize türlerinin sıralama anahtarları ASCII karakter kodları ile sıralanır. ASCII’da Dolar simgesi ($) pound simgesinden (#) hemen sonra gelir. Böylece UserGameMapping (KullanıcıOyunEşleme) varlığından hepimizin eşleşme almasını garantiye alınır.

    Bir yanıt aldığımızda veri varlıklarımızı uygulamamız tarafından bilinen nesnelere derleriz. Geri dönen ilk varlığın Oyun varlığı olduğunu biliyoruz bu yüzden varlıktan bir Oyun nesnesi üretiriz. Kalan varlıklarda, her bir varlık için bir UserGameMapping (KullanıcıOyunEşleme) nesnesi oluştururuz sonra Oyun nesnesine kullanıcı sırası ekleriz.

    Betiğin sonu işlevin kullanımını gösterir ve sonuç nesneleri çıkarır. Betiği aşağıdaki komut ile terminalinizde yürütebilirsiniz:

    python application/fetch_game_and_players.py

    Betik Oyun nesnesini ve tüm UserGameMapping (KullanıcıOyunEşleme) nesnelerini konsola çıkartmalıdır.

    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>
    

    Bu betik tablonuzu nasıl modelleyebileceğinizi ve tek bir DynamoDB isteğinde çoklu varlık türleri alabileceğinizi gösterir. İlişkisel bir veri tabanında birleşimleri kullanarak tek bir istekte farklı tablolardan çoklu varlık türleri alabilirsiniz. DynamoDB ile birlikte erişmeniz gereken varlıkların tek bir tabloda birbirinin yanında konumlanması için verilerinizi özellikle modellersiniz. Bu yaklaşım tipik bir ilişkisel veri tabanında birleşime olan ihtiyacınızın yerine geçer ve siz ölçeklendirdikçe uygulamanızı yüksek performansta tutar.


    Bu modülde birincil bir anahtar tasarladık ve bir tablo oluşturduk. Sonra verileri tabloya toplu yükledik ve tek bir istekte çoklu varlık türleri için nasıl sorgu oluşturulacağını gördük.

    Mevcut birincil anahtar tasarımı ile aşağıdaki erişim modellerini karşılayabiliriz:

    • Kullanıcı profili oluştur (Yaz)
    • Kullanıcı profilini güncelle (Yaz)
    • Kullanıcı profili edin (Oku)
    • Oyun oluştur (Yaz)
    • Oyun görüntüle (Oku)
    • Bir kullanıcı için oyuna katıl (Yaz)
    • Oyunu başlat (Yaz)
    • Bir kullanıcı için oyunu güncelle (Yaz)
    • Oyunu güncelle (Yaz)

    Sonraki modülde ikincil bir dizin ekleyeceğiz ve seyrek dizin tekniğini öğreneceğiz. İkincil dizinler DynamoDB tablonuzda ek erişim modellerini desteklemenizi sağlar.