DynamoDB ve NoSQL’e yeni başlayan kullanıcılar için en büyük düzenlemelerden biri, bütün bir veri kümesinde verilerin nasıl tasarlanacağına ilişkindir. Örneğin, bizim oyunumuzda kullanıcılara hangi oyun oturumlarına katılabileceklerini gösterebilmek için açık spotları olan oyun oturumları bulmamız gerekmektedir.
İlişkisel bir veri tabanında veri sorgulamak için birtakım SQL unsuru yazarsınız.

SELECT * FROM games
	WHERE status = “OPEN”

DynamoDB, bir Sorgu kapsamında ya da Tarama operasyonu sırasında sonuçları filtreleyebilmekte ancak bir ilişkisel veri tabanı olarak faaliyet göstermemektedir. Bir DynamoDB filtresi, Sorgu ya da Tarama faaliyetine uyumlu olan ilk ögelerin geri alınmasıyla birlikte devreye girmektedir. Filtre DynamoDB hizmetinden gönderilen ödeme yükünün boyutunu azaltmaktadır ancak en başta geri alınan ögelerin sayısı DynamoDB boyut sınırlarına bağlıdır.

Neyse ki DynamoDB kapsamında veri kümenize yönelik olarak filtreli sorguları etkinleştirebilmenizin birtakım yolları vardır. DynamoDB tablosunda verimli filtreler elde edebilmek için en başından itibaren tablonuzun veri modeline yönelik olarak filtreleri planlamanız gerekmektedir. Bu laboratuvarın ikinci modülünde öğrendiğimiz dersi unutmayın: Erişim modellerinizi düşünün ve tablonuzu ona göre tasarlayın.

Aşağıdaki adımlarda açık oyunları bulmak için bir genel ikinci dizin kullanmaktayız. Ayrıca, bu erişim modelini kontrol etmek için seyrek dizin tekniğini kullanacağız.

Modülü Tamamlama Süresi: 40 Dakika


  • Adım 1: Bir seyrek ikincil dizin tasarlayın

    İkincil dizinler DynamoDB’deki önemli veri tasarlama araçlarıdır. Alternatif sorgu modelleri için verilerinizi yeniden biçimlendirmenize olanak tanırlar. Bir ikincil dizin oluşturmak için daha önce bir tablo oluşturduğunuz gibi dizinin birincil anahtarını belirlersiniz. Bir genel ikincil dizine yönelik birincil anahtarın her öge için eşsiz olmasına gerek yoktur. DynamoDB daha sonra bildirilen özniteliklere göre ögeleri dizine kopyalar ve tablonuzda yaptığınız gibi sorgulama yapabilirsiniz.

    Seyrek ikincil dizinlerin kullanımı DynamoDB açısından gelişmiş bir stratejidir. DynamoDB ikincil dizinde yer alan birincil anahtarın unsurlarına sahip olmaları durumunda ikincil dizinlerle birlikte orijinal tablodan öge kopyalamaktadır. Birincil anahtar ögelerine sahip olmayan unsurlar kopyalanmamaktadır. Bu nedenle, söz konusu ikincil dizinlere “seyrek” adı verilmektedir.

    Bu durumun bizim açımızdan nasıl olduğuna bir bakalım. Açık oyunların bulunması için iki erişim modelimiz olduğunu hatırlayabilirsiniz.

    • Açık oyun bul (Oku)
    • Eşlemeye göre açık oyun bul (Oku)

    Bir birleşik birincil anahtar kullanmak suretiyle KARMA anahtarın oyuna yönelik harita özniteliği olduğu ve ARALIK anahtarının oyuna yönelik open_timestamp özniteliğini taşıdığı (bu durum oyunun açıldığı zamanı göstermektedir) bir ikincil dizin oluşturabiliriz.

    Burada bizim için önemli olan durum, bir oyun tamamen dolduğunda open_timestamp özniteliğinin silinmesidir. Öznitelik silindiğinde dolmuş olan oyun ikincil dizinden silinmektedir çünkü ARALIK anahtarı özniteliğine yönelik bir değer bulunmamaktadır. Dizinimiz böyle seyrek kalmaktadır: Sadece open_timestamp özniteliği olan açık oyunlar bulunmaktadır.

    Bir sonraki adımda ikincil dizini oluşturmaktayız.

  • Adım 2: Bir seyrek ikincil dizin oluşturun

    Bu adımda açık oyunlar için (henüz dolmamış olan oyunlar için) yedek ikincil dizin oluşturmaktayız.

    Bir ikincil dizinin oluşturulması bir tablonun oluşturulmasına benzemektedir. İndirdiğiniz kodda add_secondary_index.py adını taşıyan betikler/ alt dizin içerisinde bir betik göreceksiniz. Bu dosyanın içerikleri aşağıdaki gibidir:

    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)

    Öznitelikler bir tablo ya da ikincil dizin için bir birincil anahtarda kullanıldıklarında AttributeDefinitions (Öznitelik Tanımları) kapsamında tanımlanmalıdır. Ardından GlobalSecondaryIndexUpdates (Küresel İkincil Dizin Güncellemeleri) özelliği kapsamında yeni bir ikincil dizin Oluştururuz. Söz konusu ikincil dizin için dizin adını, birincil anahtar şemasını, öngörülen aktarım hızını ve planlamak istediğimiz öznitelikleri belirleriz.

    İkincil dizinimizin bir seyrek dizin olarak kullanılmak üzere oluşturulduğunu belirtme ihtiyacı duymadığımızı bildirmek isteriz. Bu, tamamen girmiş olduğunuz verinin işlevidir. İkincil dizinlerinize yönelik özniteliklere sahip olmayan ögeleri tablonuza yazarsanız söz konusu ögeler ikincil dizininize dâhil olmayacaktır.

    Aşağıdaki komutu çalıştırarak ikincil dizininizi oluşturun.

    python scripts/add_secondary_index.py

    Konsolda şu mesajı görmeniz gerekmektedir: “Tablo başarıyla güncellendi.”

    Sonraki adımda açık oyunları harita üzerinde bulmak için seyrek dizini kullanırız.

  • Adım 3: Seyrek ikincil dizini sorgulayın

    İkincil dizini yapılandırdığımıza göre bazı erişim modellerine yönelik olarak bu dizini kullanalım.

    Bir ikincil dizin kullanmak için iki API çağrınız bulunmaktadır: Sorgula ve Tara. Sorgula seçeneği ile KARMA anahtarı tanımlamanız gerekmektedir. Böylelikle, istenen sonuca ulaşabilirsiniz. Tarama seçeneğiyle birlikte bir KARMA anahtar tanımlamazsınız. Operasyon da tüm tablo boyunca sürmektedir. Taramalar bazı özel durumlar haricinde DynamoDB’de pek desteklenmemektedir çünkü veri tabanınızda her ögeye erişim sağlamaktadır. Tablonuzda ciddi miktarda veri varsa tarama işlemi çok uzun sürebilir. Bir sonraki aşamada seyrek dizinler ile kullanıldığında Taramaların neden güçlü bir araç olabileceğini göstermekteyiz.

    Açık olan tüm oyunları harita adına göre bulmak için bir önceki adıma oluşturduğumuz ikinci dizine yönelik olarak Sorgulama API unsurunu kullanabiliriz. İkincil dizin harita adına göre bölümlere ayrılmakta ve açık oyunların bulunması noktasında istenen sorguları yapmamızı sağlamaktadır.

    İndirdiğiniz kod içerisinde find_open_games_by_map.py adına sahip bir dosya uygulamada/ dizinde yer almaktadır. Bu betiğin içerikleri şu şekildedir.

    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)

    Önceki betikte find_open_games_by_map işlevi uygulamanızda yer alan işleve benzerdir. İşlev bir harita adı kabul etmekte ve haritaya yönelik tüm açık oyunları bulmak için OpenGamesIndex (Açık Oyun Dizini) sorgusu yapmaktadır. Daha sonra sonuçta elde edilen varlıkları uygulamanızda kullanılabilen Oyun nesnelerine eklemektedir.

    Bu betiği aşağıdaki komutu terminalinizde çalıştırarak yürütün:

    python application/find_open_games_by_map.py

    Terminal, Green Grasslands haritası için dört açık oyunla birlikte şu çıktıları gösterecektir.

    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/

    Sonraki adımda seyrek ikincil dizini taramak için Tarama API seçeneğini kullanırız.

  • Adım 4: Seyrek ikincil dizini tarayın

    Bir önceki adımda özel bir harita kapsamında oyunları nasıl bulabileceğimizi gördük. Bazı oyuncular özel bir haritada oynamayı tercih edebilmektedir. Bu nedenle, bu işlem yararlıdır. Diğer oyuncular herhangi bir haritada oynamak isteyebilirler. Bu bölümde harita türü ne olursa olsun uygulamada açık oyunların nasıl bulunacağını gösteriyoruz. Bu işlem için Tarama API seçeneğini kullanıyoruz.

    Genel olarak DynamoDB Tarama operasyonunu kullanmak için tablonuzu tasarlamanıza gerek yoktur çünkü DynamoDB ihtiyacınız olan varlıkları bulan hassas sorgular için üretilmiştir. Bir Tarama operasyonu tablonuza rastgele varlık koleksiyonları bulmaktadır. Bu nedenle, ihtiyacınız olan varlıkların bulunması veri tabanına birden fazla kere dönülüp bakılmasını gerektirebilmektedir.

    Bununla beraber, bazen Tarama işlevi de yararlı olabilmektedir. Biz seyrek bir ikincil dizine sahibiz. Yani, dizinimizde bu kadar varlık olmamalıdır. Ayrıca, dizine sadece açık olan oyunlar dâhildir. Bu da tam ihtiyacımız olan şeydir.

    Bu bağlamda Tarama işlevi mükemmel çalışmaktadır. Nasıl çalıştığına bir bakalım. İndirdiğiniz kodda find_open_games.py adında bir dosya uygulamada/ hedef konumda yer almaktadır. Dosyanın içeriği aşağıdaki gibidir.

    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)

    Bu kod, bir önceki aşmada yer alan koda benzerdir. Bununla beraber, DynamoDB isteminde sorgulama() yöntemini kullanmak yerine tarama() yöntemini tercih ediyoruz. Tarama() işlevini kullandığımız için sorgulama() işlevi kapsamında yaptığımız gibi önemli koşulları belirtmemize gerek yoktur. Bu işlemle DynamoDB’nin sırasız şekilde birtakım ögeler bulmasını sağlıyoruz.

    Terminalinizde şu komutla betiği çalıştırın.

    python application/find_open_games.py

    Terminaliniz farklı haritalarda açık olan dokuz oyunluk bir listeyi yazdırmalıdır.

    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>
    

    Bu aşamada Tarama faaliyetinin kullanımının belirli durumlarda nasıl doğru seçim olabileceğini gördük. Açık oyunları oyunculara göstermek amacıyla seyrek ikincil dizinimizden çeşitli varlıkları bulmak için Tarama işlevini kullandık.

    Sonraki adımlarda iki erişim modeli uygularız:

    • Bir kullanıcı için oyuna katıl (Yaz)
    • Oyunu başlat (Yaz)

    Aşağıdaki adımlarda “Bir kullanıcı için oyuna katıl” erişim modelini uygulamak için DynamoDB işlemleri kullanacağız. Veri unsurlarını kısa sürede etkileyen ilişkisel faaliyetlerde bu işlemler çok yaygındır. Örneğin, bir banka işlettiğinizi düşünün. Alejandra adında bir müşteri, Ana adındaki diğer bir müşteriye 100 USD gönderiyor. Bu işlemin kaydı sırasında değişikliklerin her iki müşteri hesabına yansıdığından emin olmak için işlem özelliği kullanırsınız.

    DynamoDB işlemleri, tek bir faaliyetin bir parçası olarak birden fazla ögeyi değiştiren uygulamaların oluşturulması sürecini kolaylaştırmaktadır. İşlemlerle birlikte tek bir işlem isteminde en fazla 10 adet öge yürütebilirsiniz.

    Bir TransactWriteItem (İşlem Yazdırma Ögesi) API çağrısı kapsamında şu faaliyetleri kullanabilirsiniz:

    • Yerleştir: Bir ögenin yerleştirilmesi veya ögenin üzerine yazım için.
    • Güncelle: Mevcut bir ögenin güncellenmesi için.
    • Sil: Bir ögenin silinmesi için.
    • Koşul Kontrolü: Ögeyi değiştirmeden ögeye bir koşul eklemek için.

     

    Sonraki aşama kapsamında oyunun aşırı düzeyde dolmasını engellerken oyuna yeni kullanıcı eklendiği sırada bir DynamoDB işlemi kullanırız.

  • Adım 5: Bir oyuna kullanıcılar ekleyin

    Bu modülde değindiğimiz ilk erişim modeli, bir oyuna yeni kullanıcıların eklenmesidir.

    Bir oyuna yeni kullanıcı eklerken şunlara ihtiyaç duyarız:

    • Oyunda 50’den az oyuncu olduğunun doğrulanması (her oyunda en fazla 50 oyuncu olabilir).
    • Kullanıcının hâlihazırda oyunda olmadığının doğrulanması.
    • Kullanıcının oyuna eklenmesi için yeni bir UserGameMapping (KullanıcıOyunEşleme) varlığının oluşturulması.
    • Kaç oyuncunun oyunda olduğunu görmek için Oyun varlığı üzerindeki birey özniteliklerinin arttırılması.

    Tüm bu faaliyetlerin yerine getirilmesi mevcut Oyun varlıklarında yazım eylemleri ve yeni UserGameMapping (KullanıcıOyunEşleme) varlığı, bununla beraber her bir varlığa yönelik olarak koşullu mantık gerektirmektedir. Bu işlem DynamoDB işlemleri için mükemmel olan bir faaliyettir çünkü aynı talepte birden fazla varlık üzerinde çalışmanız gerekmektedir ve tüm varlık taleplerinin toplu şekilde başarılı ya da başarısız olmasını istersiniz.

    İndirilen kod kapsamında uygulamada/ hedef konumda join_game.py betiği yer almaktadır. Bu betikte yer alan işlev, bir oyuna bir kullanıcı eklemek için DynamoDB işlemi kullanmaktadır.

    Betik içeriği aşağıdaki gibidir.

    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)

    Bu betik içerisindeki join_game_for_user işlevi kapsamında transact_write_items() yöntemi ile bir yazım işlemi yapılır. Bu işlemde iki faaliyet yer almaktadır.

    Bu işlemin ilk faaliyeti kapsamında yeni bir UserGameMapping (KullanıcıOyunEşleme) varlığı yerleştirmek için Yerleştir faaliyetini kullanırız. Bu faaliyet kapsamında SK özniteliğinin bu varlık için gerçekleşmemesinin gerektiği bir koşul belirleriz. Böylelikle, PK ve SK unsurlarına sahip olan bir varlık da hâlihazırda var olmaz. Böyle bir varlık hâlihazırda var olsaydı bu kullanıcının zaten oyuna katıldığı anlamına gelirdi.

    İkinci faaliyet tek seferde birey özniteliğinin arttırılması için Oyun üzerinde bir Güncelleme faaliyetidir. Bu faaliyetin bir parçası olarak kişi değerinin 50’den büyük olmadığından emin olmak için bir koşullu kontrol sağlarız. Bir oyuna 50 kişi katılır katılmaz oyun dolar ve başlamaya hazır hâle gelir.

    Bu betiği aşağıdaki komut ile terminalinizde çalıştırın:

    python application/join_game.py

    Terminalinizdeki çıktı, oyuna kullanıcı eklendiğini göstermelidir.

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

    Betiği tekrar çalıştırmayı denemeniz durumunda işlevin başarısız olacağını unutmayın. vlopez adlı kullanıcı oyuna zaten eklenmiştir. O nedenle, aynı kullanıcının tekrar eklenmesi belirlediğimiz koşulları karşılamamaktadır.

    DynamoDB işlemlerinin eklenmesi bu gibi karmaşık faaliyetler kapsamında iş akışını sadeleştirmektedir. Bu işlemler olmadan sorun oluşması hâlinde karmaşık koşulları ve manuel geri çağırma süreçlerini içeren birden fazla API çağrısı gerekirdi. Bunun gibi karmaşık faaliyetleri 50 satırdan daha az kodla uygulayabiliyoruz.

    Sonraki adımda “Oyuna başla (Write)” erişim modelini uygularız.

  • Adım 6: Bir oyuna başlayın

    Bir oyunda 50 kullanıcı olduğunda oyunu tasarlayan kişi oynanabilirliği başlatmak amacıyla oyuna başlayabilir. Bu aşamada söz konusu erişim modelinin nasıl kullanılacağını göstereceğiz.

    Uygulamamız arka uçta oyunu başlatmak için bir istek aldığında üç şeyi kontrol ederiz:

    • Oyunda 50 kişinin olup olmadığı.
    • İstemci kullanıcının oyunu tasarlayan kişi olup olmadığı.
    • Oyunun henüz başlayıp başlamadığı.

    Bu kontrollerden her birini oyunu güncelleme istemi kapsamında koşul ifadesi içerisinde işleyebiliriz. Tüm bu kontrollerin başarılı olması durumunda varlığımızı şu yollarla güncelleriz:

    • Önceki modülden seyrek ikincil dizin kapsamında bir açık oyun olarak görülmemesi için open_timestamp özniteliğini kaldırın.
    • Oyunun başladığı zamanı göstermek için bir start_time özniteliği ekleyin.

    İndirilen kod kapsamında uygulamada/ hedef konumda start_game.py betiği yer almaktadır. Dosyanın içerikleri aşağıdaki gibidir.

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

    Betik içerisindeki start_game işlevi uygulamanızda yer alan işleve benzerdir. Bu minvalde game_id, requesting_user, ve start_time gerekmektedir ve oyunun başlatılması için Oyun varlığının güncellenmesi adına bir istek çalıştırılmaktadır.

    update_item() çağrısındaki ConditionExpression (Koşul İfadesi) parametresi bu aşamada listelediğimiz üç kontrol unsurunun her birini belirlemektedir—oyunda 50 kişi olmalı ve oyunun başlamasını isteyen kullanıcı oyunu tasarlayan kişi olmalıdır. Oyunda start_time özniteliği olamaz çünkü bu öznitelik oyunun zaten başladığını göstermektedir.

    UpdateExpression (Güncelleme İfadesi) parametresi kapsamında varlığımıza yapmak istediğimiz değişiklikleri görebilirsiniz. İlk olarak varlıktan open_timestamp özniteliğini kaldırırız ve oyunun başlangıç saatine yönelik olarak start_time özniteliğini belirleriz.

    Terminalinizde aşağıdaki komutla bu betiği çalıştırın.

    python application/start_game.py

    Terminalinizde oyunun çıktısını görmelisiniz. Bu çıktı oyunun başarıyla başladığını göstermektedir.

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

    Terminalinizde betiği tekrar yürütmeyi deneyin. Bu sefer oyunu başlatamadığınızı gösteren bir hata mesajı görmeniz gerekmektedir. Bu durumun sebebi oyunu hâlihazırda başlatmış olmanız ve start_time özniteliğinin zaten var olmasıdır. Sonuç olarak istek, varlık üzerinde koşullu kontrol açısından başarısız olmuştur.

    Oyun varlığı ve ilişkili Kullanıcı varlıkları arasında çoka çok ilişkisi olduğunu ve ilişkinin Kullanıcı Oyun Eşleme varlığı tarafından temsil edildiğini hatırlayabilirsiniz.

    Çoğu zaman ilişkinin her iki tarafını da sorgulamak istersiniz. Birincil anahtar oluşumumuz ile birlikte bir Oyun kapsamındaki tüm Kullanıcı varlıklarını bulabiliriz. Döndürülmüş bir dizin kullanmak suretiyle bir Kullanıcıya yönelik tüm Oyun varlıklarının sorgulanmasını etkinleştirebiliriz.

    DynamoDB kapsamında bir döndürülmüş dizin, birincil anahtarınızın tam tersi olan bir ikincil dizindir. Bu ARALIK anahtarı sizin KARMA anahtarınız olmaktadır ya da bunun tam tersi de olmaktadır. Bu model tablonuzu döndürmekte ve çoka çok ilişkilerinizin diğer tarafında sorgulama yapmanıza olanak tanımaktadır.

    Aşağıdaki adımlarda tabloya bir döndürülmüş dizin ekliyoruz ve özel bir Kullanıcı için tüm Oyun varlıklarının nasıl alınacağını gösteriyoruz. 

  • Adım 7: Bir döndürülmüş dizin ekleyin

    Bu adımda tabloya bir döndürülmüş dizin ekliyoruz. Bir döndürülmüş dizin diğer ikincil dizinler gibi oluşturulmaktadır.

    İndirdiğiniz kodda, add_inverted_index.py betiği betikler/ dizinindedir. Bu Python betiği tablonuza bir döndürülmüş dizin ekler.

    Bu dosyanın içerikleri aşağıdaki gibidir.

    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)
    

    Bu betik kapsamında DynamoDB isteği üzerinde update_table() yöntemi çağırmaktayız. Yöntem kapsamında oluşturmak istediğimiz dizin anahtar şeması, öngörülen iş ve dizine yansıtılacak öznitelikler dâhil olmak üzere ikincil dizin hakkındaki detayları iletmekteyiz.

    Terminalinizde aşağıdaki komutu yazarak betiği çalıştırın:

    python scripts/add_inverted_index.py

    Terminaliniz dizininizin başarıyla oluşturulduğuna dair bir çıktı görüntüleyecektir.

    Table updated successfully.

    Bir sonraki aşamada özel bir Kullanıcı için tüm Oyunları almak adına döndürülmüş dizin kullanırız.

  • Adım 8: Bir kullanıcı için oyunlar alın

    Döndürülmüş dizini oluşturduğumuza göre bir Kullanıcı tarafından oynanan Oyun varlıklarını almak için kullanım aşamasına geçebiliriz. Bu süreç içerisinde Oyun varlıklarını görmek istediğimiz Kullanıcı ile döndürülmüş dizini sorgulamamız gerekir.

    İndirilen kod kapsamında uygulamada/ dizinindefind_games_for_user.py betiği yer almaktadır. Dosyanın içerikleri aşağıdaki gibidir.

    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)
    

    Bu betik içerisinde oyununuzda sahip olacağınız işleve benzer olan find_games_for_user() işlevi yer almaktadır. Bu işlev bir kullanıcı adını kullanarak o kullanıcının oynadığı oyunları bildirmektedir.

    Betiği aşağıdaki komutla terminalinizde çalıştırın:

    python application/find_games_for_user.py

    Betik carrpatrick adlı kullanıcının oynadığı tüm oyunları yazdırmalıdır.

    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>
    

    Bu modülde tabloya bir ikincil dizin ekledik. Bu işlemle iki ek erişim modeli ortaya çıktı:

    • Eşlemeye göre açık oyun bul (Oku)
    • Açık oyun bul (Oku)

    Bunun gerçekleştirilmesi için hâlâ ek oyunculara açık olan oyunları içeren bir seyrek dizin kullandık. Ardından açık oyunları bulmak için dizin kapsamında hem Sorgulama hem de Tarama API’leri kullandık.

    Ayrıca, uygulama içerisinde iki gelişmiş yazma faaliyetinin nasıl yürütüleceğini de gördük. İlk olarak, bir kullanıcı oyuna girdiğinde DynamoDB işlemlerini kullandık. İşlemlerle birlikte tek bir istek kapsamında birden farklı varlık içerisinde karmaşık koşullu yazma işlemlerini gerçekleştirdik.

    Ardından, hazır olduğunda başlaması için oyun tasarımcıları için işlevi hayata geçirdik. Bu erişim modeli içerisinde üç özniteliğin kontrol edilmesini ve iki özniteliğin güncellenmesini gerektiren bir güncelleme faaliyeti yürüttük. Koşul ifadeleri ve güncelleme ifadeleri yoluyla tek bir istek içerisinde bu karmaşık mantığı ifade edebilirsiniz.

    Dahası, bir Kullanıcı tarafından oynanan tüm Oyun varlıklarını alarak son erişim modellerine ulaştık. Bu erişim modelinin işlenmesi için Kullanıcı varlıkları ve Oyun varlıkları arasındaki çoka çok ilişkinin diğer tarafında sorgulama faaliyetine olanak tanımak için dönüştürülmüş dizin modelini kullanarak bir ikincil dizin oluşturduk.

    Bir sonraki modülde oluşturduğumuz kaynakları temizleyeceğiz.