Una delle maggiori modifiche per gli utenti che sono nuovi a DynamoDB e NoSQL è come modellare i dati per filtrare un intero set di dati. Ad esempio, nel nostro gioco dobbiamo trovare sessioni di gioco con spot aperti per mostrare agli utenti a quale sessione di gioco possono partecipare.
In un database relazionale, scriveresti alcuni SQL per effettuare query dei dati.

SELECT * FROM games
	WHERE status = “OPEN”

DynamoDB può filtrare i risultati su un'operazione di Query o Scan, ma non si comporta come un database relazionale. Un filtro DynamoDB si applica dopo aver recuperato gli elementi iniziali che corrispondono all'operazione di Query o Scan. Il filtro riduce le dimensioni del payload inviato dal servizio DynamoDB, ma il numero di elementi recuperati inizialmente è soggetto ai limiti di dimensione di DynamoDB.

Fortunatamente, ci sono diversi modi con i quali puoi permettere query filtrate nel tuo set di dati di DynamoDB. Per offrire filtri efficaci sulla tua tabella DynamoDB, devi pianificare i filtri nel tuo modello di dati della tabella fin dall'inizio. Ricorda quello che abbiamo imparato nel secondo modulo di questo laboratorio: considera i tuoi modelli di accesso e poi progetta la tabella.

Nelle prossime fasi, utilizziamo un indice secondario globale per trovare partite aperte. Nello specifico, utilizzeremo la tecnica dell'indice di tipo sparse per gestire questo modello di accesso.

Tempo necessario per completare il modulo:40 minuti


  • Fase 1: Crea un modello di indice secondario di tipo sparse

    Gli indici secondari sono strumenti di creazione di modelli di dati fondamentali in DynamoDB. Ti permettono di rimodellare i tuoi dati per permettere modelli di query alternativi. Per creare un indice secondario, specifica la chiave primaria dell'indice, proprio come quando hai creato precedentemente la tabella. Ricorda che la chiave primaria per un indice secondario globale non deve essere univoca per ogni elemento. Poi, DynamoDB copia gli elementi nell'indice sulla base degli attributi specificati, quindi puoi effettuarvi le query proprio come quando fai la tabella.

    L'utilizzo di indici secondari di tipo sparse è una strategia avanzata di DynamoDB. Con gli indici secondari, DynamoDB copia gli elementi dalla tabella originale solo se questi hanno tutte le caratteristiche della chiave primaria nell'indice secondario. Gli elementi che non corrispondono alla chiave primaria non vengono copiati e, per questo, questi indici secondari vengono chiamati "di tipo sparse".

    Vediamo come funziona. Forse ti ricordi che abbiamo due modelli di accesso per trovare partite aperte:

    • Trova partite aperte (Leggi)
    • Trova partite aperte tramite la mappa (Leggi)

    Possiamo creare un indice secondario utilizzando una chiave primaria composta dove la chiave HASH è l'attributo della mappa per la partita e la chiave RANGE è l'attributo open_timestamp per il gioco e indica quando è stato aperto il gioco.

    La parte importante per noi è che, quando la partita è completa, l'attributo open_timestamp venga eliminato. Quando l'attributo viene eliminato, la partita completa viene rimossa dall'indice secondario perché non ha un valore per l'attributo della chiave RANGE. Ecco cosa rende il nostro indice un indice di tipo sparse: include solo partite aperte con l'attributo open_timestamp.

    Nella prossima fase, creiamo l'indice secondario.

  • Fase 2: Crea un indice secondario di tipo sparse

    In questa fase, creiamo un indice secondario di tipo sparse per le partite aperte (partite non ancora complete).

    La creazione di un indice secondario è simile alla creazione di una tabella. Nel codice che hai scaricato, troverai un file script in scripts/ directory denominati add_secondary_index.py. I contenuti del file sono riportati di seguito.

    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)

    Quando gli attributi vengono utilizzati in una chiave primaria per una tabella o un indice secondario, devono essere definiti in AttributeDefinitions. Poi, dobbiamo creare un nuovo indice secondario nella proprietà GlobalSecondaryIndexUpdates. Per questo indice secondario, specifichiamo il nome dell'indice, lo schema della chiave primaria, il throughput su cui si è effettuato il provisioning e gli attributi che vogliamo mostrare.

    Ricorda che non dobbiamo specificare che il nostro indice secondario verrà utilizzato come indice di tipo sparse. Quella è solo una funzione dei dati che inserirai. Se scrivi nella tua tabella elementi che non hanno gli attributi per i tuoi indici secondari, tali elementi non verranno inclusi nell'indice.

    Crea il tuo indice secondario eseguendo il seguente comando.

    python scripts/add_secondary_index.py

    Dovresti visualizzare nella console il messaggio: "Tabella aggiornata con successo".

    Nella prossima fase, utilizziamo l'indice di tipo sparse per trovare partite aperte tramite la mappa.

  • Fase 3: Effettua query sull'indice secondario di tipo sparse

    Adesso che abbiamo configurato l'indice secondario, lo utilizzeremo per soddisfare alcuni modelli di accesso.

    Per utilizzare un indice secondario, hai due chiamate API disponibili: Query e Scan. Con Query, devi specificare la chiave HASH e otterrai il risultato fissato. Con Scan, non devi specificare una chiave HASH e l'operazione verrà eseguita sull'intera tabella. Le chiamate Scan sono sconsigliate in DynamoDB tranne in circostanze specifiche perché accedono a tutti gli elementi nel tuo database. Se hai un gran numero di dati nella tua tabella, l'operazione di scanning può richiedere molto tempo. Nella prossima fase, ti mostriamo perché Scan può essere uno strumento potente se utilizzato con gli indici di tipo sparse.

    Possiamo utilizzare le API Query sull'indice secondario che abbiamo creato nella fase precedente per trovare partite aperte tramite il nome della mappa. L'indice secondario è ripartito secondo il nome della mappa e ci permette di effettuare query mirate per trovare partite aperte.

    Nel codice che hai scaricato, c'è un file find_open_games_by_map.py nell' applicazione/ directory. Seguono i contenuti di questo script.

    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)

    Nello script precedente, la funzione find_open_games_by_map è simile a una funzione che avresti nella tua applicazione. La funzione accetta il nome di una mappa ed effettua query sul OpenGamesIndex per trovare partite aperte per la mappa. Successivamente, mette insieme le entità risultate negli oggetti Partita che possono essere utilizzati nella tua applicazione.

    Lancia questo script eseguendo il seguente comando nel tuo terminale.

    python application/find_open_games_by_map.py

    Il terminale mostrerà il seguente output con quattro partite aperte per la mappa di 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/

    Nella prossima fase, utilizziamo l'API Scan per scansionare l'indice secondario di tipo sparse.

  • Fase 4: Scansiona l'indice secondario di tipo sparse

    Nella fase precedente, abbiamo visto come trovare giochi per una mappa particolare. Alcuni giocatori preferiscono utilizzare una mappa specifica, quindi questo è molto utile. Altri, invece, vogliono giocare con una mappa qualsiasi. In questa sezione, ti mostriamo come trovare partite aperte nell'applicazione indipendentemente dal tipo di mappa. Per farlo, utilizziamo l'API Scan.

    In generale, non vuoi progettare la tabella per utilizzare l'operazione Scan di DynamoDB perché DynamoDB è creato per le query precise che trovano l'esatta entità di cui hai bisogno. Un'operazione Scan trova una raccolta casuale di entità nella tua tabella, quindi individuare le entità di cui hai bisogno può richiedere diverso tempo all'interno del database.

    Tuttavia, a volte l'operazione Scan può essere utile. Nel nostro caso, abbiamo un indice secondario di tipo sparse, quindi il nostro indice non dovrebbe avere così tante entità al suo interno. Inoltre, l'indice include solo le partite aperte, ovvero esattamente quello di cui abbiamo bisogno.

    Per questo caso d'uso, l'operazione Scan funziona perfettamente. Vediamo come. Nel codice che hai scaricato, c'è un file find_open_games.py nell'applicazione/directory. Seguono i contenuti del file.

    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)

    Questo codice è simile a quello della fase precedente. Tuttavia, piuttosto che utilizzare il metodo query() sul client di DynamoDB, utilizziamo il metodo scan(). Dato che stiamo utilizzando scan(), non dobbiamo specificare ulteriori dettagli riguardo alle condizioni della chiave come abbiamo fatto con query(). Facciamo solo in modo che DynamoDB restituisca una serie di elementi senza un ordine specifico.

    Eseguire lo script nel terminale con il comando seguente.

    python application/find_open_games.py

    Il tuo terminale dovrebbe emettere una lista di nove partite aperte in diverse mappe.

    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>
    

    In questa fase, abbiamo visto come l'utilizzo dell'operazione Scan può essere la scelta giusta in certe situazioni. Abbiamo utilizzato Scan per ottenere una serie di entità dal nostro indice secondario di tipo sparse per mostrare le partite aperte ai giocatori.

    Nelle prossime fasi, proveremo a soddisfare due modelli di accesso:

    • Partecipa a una partita per un utente (Scrivi)
    • Avvia partita (Scrittura)

    Per soddisfare i modelli di accesso "Partecipa a una partita per un utente" nella fase successiva, utilizzeremo le Transazioni di DynamoDB. Le transazioni sono molto utilizzate nei sistemi relazionali per le operazioni che riguardano tanti elementi con più dati allo stesso tempo. Ad esempio, immagina di gestire una banca. Una cliente, Alejandra, invia 100 USD a un'altra cliente, Ana. Al momento di registrare la transazione, utilizzeresti una transazione per assicurarti che le modifiche vengano applicate al bilancio di entrambe le clienti e non solo a una delle due.

    Le transazioni di DynamoDB facilitano la creazione di applicazioni che alterano gli elementi multipli come parte di un'unica operazione. Con le transazioni, puoi lavorare su un massimo di 10 elementi per una singola richiesta di transazione.

    In una chiamata API TransactWriteItem, puoi utilizzare le seguenti operazioni:

    • Put: per inserire o sovrascrivere un elemento.
    • Upgrade: per aggiornare un elemento pre-esistente.
    • Delete: per rimuovere un elemento.
    • ConditionCheck: per controllare la condizione di un elemento già esistente senza alterarlo.

     

    Nella prossima fase, utilizziamo una transazione di DynamoDB durante l'aggiunta di nuovi utenti a un gioco evitando allo stesso tempo che il gioco si sovraccarichi.

  • Fase 5: Aggiungi utenti alla partita

    Il primo modello di accesso al quale facciamo riferimento in questo modulo è l'aggiunta di nuovi utenti a una partita.

    Quando aggiungiamo un nuovo utente a una partita, dobbiamo:

    • Confermare che non ci siano già 50 giocatori nella partita (ogni partita può avere al massimo 50 giocatori).
    • Confermare che l'utente non sia già all'interno della partita.
    • Creare una nuova entità UserGameMapping per aggiungere l'utente alla partita.
    • Incrementa l'attributo delle persone sull'entità Partita per tracciare il numero di giocatori nel gioco.

    Ricorda che la messa in pratica di tutte queste misure richiede di scrivere le azioni nell'entità Partita già esistente e nella nuova entità UserGameMapping , oltre alla condizione di ogni entità. Questo è il tipo di operazione che si combina perfettamente con le transazioni di DynamoDB, perché devi lavorare su diverse entità nella stessa richiesta con l'obbiettivo che tale richiesta abbia successo o fallisca.

    Nel codice che hai scaricato, c'è uno script join_game.py nell'applicazione/ directory. La funzione nello script utilizza una transazione DynamoDB per aggiungere un utente a una partita.

    Seguono i contenuti dello script.

    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)

    Nella funzione dello script join_game_for_user, il metodo transact_write_items() permette una transazione di scrittura. Questa transazione consiste in due operazioni.

    Nella prima operazione della transazione, utilizziamo un'operazione Put per inserire una nuova entità UserGameMapping. Come parte di tale operazione, specifichiamo che l'attributo SK non dovrebbe esistere per questa entità. Questo assicura che un'entità con questi PK E SK non esista già. Se tale entità esiste già, significa che l'utente sta già partecipando al gioco.

    La seconda operazione è un'operazione Update sull'entità Partita per incrementare l'attributo delle persone di uno. Come parte di questa operazione, aggiungiamo un controllo condizionale per assicurarci che il valore attuale delle persone non superi 50. Non appena 50 persone si sono unite alla partita, questo è completo e può iniziare.

    Esegui lo script nel terminale con il comando seguente.

    python application/join_game.py

    L'output nel tuo terminale dovrebbe indicare che l'utente è stato aggiunto al gioco.

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

    Ricorda che, se proverai a eseguire nuovamente lo script, la funzione fallirà. L'utente vlopez è già stato aggiunto alla partita, quindi provare ad aggiungerlo nuovamente non soddisfa le condizioni che abbiamo specificato.

    L'aggiunta delle transazioni DynamoDB semplifica enormemente il flusso di lavoro delle operazioni complesse come queste. Senza le transazioni, questo avrebbe richiesto più chiamate API con condizioni complesse e rollback manuali in caso di conflitti. Ora possiamo implementare tali operazioni con meno di 50 linee di codice.

    Nella prossima fase, gestiamo il modello di accesso "Avvia partita (Scrivi)".

  • Fase 6: Avvia una partita

    Non appena una partita raggiunge i 50 utenti, il creatore può iniziarlo e avviare il gioco. In questa fase, mostriamo come gestire questo modello di accesso.

    Quando il backend della nostra applicazione riceve una richiesta per iniziare la partita, controlliamo tre aspetti:

    • Il gioco ha 50 persone iscritte.
    • L'utente richiedente è il creatore del gioco.
    • La partita non è ancora iniziata.

    Possiamo gestire ognuno di questi controlli in un'espressione di condizione in una richiesta per aggiornare il gioco. Se tutti questi controlli vengono superati, dobbiamo aggiornare la nostra entità nei seguenti modi:

    • Rimuovi l'attributo open_timestamp di modo che non appaia come partita aperta nell'indice secondario di tipo sparse dal modulo precedente.
    • Aggiungi un attributo start_time per indicare quando è iniziato il gioco.

    Nel codice che hai scaricato, c'è uno script start_game.py nell'applicazione/ directory. Tale file conterrà gli elementi riportati di seguito.

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

    In questo script, la funzione start_game è simile a quella che avresti nella tua applicazione. Serve una game_id, un requesting_user e uno start_time per eseguire la richiesta e aggiornare l'entità Partita per iniziare il gioco.

    Il parametro ConditionExpression nella chiamata update_item() specifica ognuno dei tre controlli che abbiamo elencato in precedenza in questa fase: il gioco deve avere 50 persone, l'utente che richiede l'inizio del gioco deve essere il creatore del gioco e il gioco non può avere un attributo start_time, che indicherebbe che è già iniziato.

    Nel parametro UpdateExpression, puoi vedere le modifiche che vogliamo fare alla nostra entità. Per prima cosa, rimuoviamo l'attributo open_timestamp dall'entità; poi impostiamo l'attributo start_time al tempo di inizio della partita.

    Esegui questo script nel terminale con il comando seguente.

    python application/start_game.py

    Dovresti vedere nel tuo terminale l'output che indica che la partita è iniziato correttamente.

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

    Prova a eseguire lo script una seconda volta nel terminale. Questa volta dovresti vedere un messaggio di errore che indica che non puoi iniziare la partita. Questo perché la partita è già iniziata, quindi l'attributo start_time esiste già. Di conseguenza, la richiesta non riesce a eseguire il controllo condizionale sull'entità.

    Forse ricordi che c'è una relazione molti-a-molti tra l'entità Partita e le entità User associati. Questa relazione viene rappresentata dall'entità UserGameMapping.

    Spesso vuoi effettuare query su entrambi i lati delle relazioni. Con le impostazioni della nostra chiave primaria, possiamo trovare tutte le entità Utente in un gioco. Possiamo abilitare le query per tutte le entità Partita per un utente utilizzando un indice inverso.

    In DynamoDB, un indice inverso è un indice secondario contrario alla chiave primaria. La chiave RANGE diventa la tua chiave HASH e viceversa. Questo modello inverte la tua tabella e ti permette di effettuare query sull'altro lato delle tue relazioni molti-a-molti.

    Nelle fasi che seguono, aggiungiamo un indice inverso alla tabella e mostriamo come utilizzarlo per recuperare tutte le entità Partita per un utente specifico. 

  • Fase 7: Aggiungi un indice inverso

    In questa fase, aggiungiamo un indice inverso alla tabella. Un indice inverso viene creato come un qualsiasi altro indice secondario.

    Nel codice che hai scaricato, c'è uno script add_inverted_index.py in scripts/ directory. Questo script Python aggiunge un indice inverso alla tua tabella.

    Tale file conterrà gli elementi riportati di seguito.

    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)
    

    In questo script, richiamiamo un metodo update_table() sul nostro client DynamoDB. In questo metodo, passiamo i dettagli sull'indice secondario che vogliamo creare, compreso lo schema della chiave per l'indice, il throughput dopo il provisioning e gli attributi dell'indice.

    Esegui lo script scrivendo il comando seguente nel tuo terminale.

    python scripts/add_inverted_index.py

    Il tuo terminale mostrerà che l'output è stato creato correttamente dal tuo indice.

    Table updated successfully.

    Nella prossima fase, utilizziamo il nostro indice inverso per recuperare tutte le entità Partita per un utente particolare.

  • Fase 8: Recupera le partite per un utente

    Ora che abbiamo creato il nostro indice inverso, utilizziamolo per recuperare le entità Partita utilizzate dall'utente. Per gestire tutto ciò, abbiamo bisogno di effettuare delle query sull'indice inverso con l'Utente del quale vogliamo vedere le entità Partita.

    Nel codice che hai scaricato, c'è uno script find_games_for_user nell'applicazione/ directory. Tale file conterrà gli elementi riportati di seguito.

    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)
    

    In questo script, abbiamo una funzione chiamata find_games_for_user() che è simile a una funzione che avresti nel tuo gioco. Questa funzione prende un nome utente e ottiene tutte le partite giocate da tale utente.

    Esegui lo script nel tuo terminale con il comando seguente.

    python application/find_games_for_user.py

    Lo script dovrebbe ottenere tutte le partite giocate dall'utente 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>
    

    In questo modulo, abbiamo aggiunto un indice secondario alla tabella. Questo ha soddisfatto due ulteriori modelli di accesso:

    • Trova partite aperte tramite la mappa (Leggi)
    • Trova partite aperte (Leggi)

    Per fare ciò, abbiamo utilizzato un indice di tipo sparse che includeva solo le partite ancora aperte per ulteriori giocatori. Poi abbiamo utilizzato le API Query e Scan sull'indice per trovare partite aperte.

    Inoltre, abbiamo visto come soddisfare due operazioni di scrittura avanzate nell'applicazione. Per prima cosa, abbiamo utilizzato le transazioni di DynamoDB quando un utente partecipava alla partita. Con le transazioni, abbiamo gestito la scrittura condizionale complessa su più entità in una sola richiesta.

    In secondo luogo, abbiamo implementato la funzione per il creatore di un gioco per iniziare la partita quando pronta. In questo modello di accesso, avevamo un'operazione di aggiornamento che richiedeva di controllare il valore di tre attributi e di aggiornare due attributi. Puoi esprimere questa logica complessa in una sola richiesta grazie alle espressioni di condizione e alle espressioni di aggiornamento.

    In terzo luogo, abbiamo soddisfatto il modello di accesso finale recuperando tutte le entità Partita utilizzate da un utente. Per gestire questo modello di accesso, abbiamo creato un indice secondario utilizzando un modello di indice inverso per permettere le query sull'altro lato della relazione molti-a-molti tra le entità dell'utente e le entità Partita.

    Nel prossimo modulo, rimuoviamo le risorse che abbiamo creato.