L’un des principaux changements apportés aux utilisateurs qui font leurs premiers pas sur DynamoDB et NoSQL réside dans la modélisation des données aux fins de filtration dans un ensemble de données. Par exemple, dans notre jeu, nous devons rechercher des sessions de jeu avec des places libres afin d’indiquer aux utilisateurs quelles sessions de jeu ils peuvent rejoindre.
Dans une base de données relationnelle, vous devrez écrire quelques instructions SQL pour interroger les données.

SELECT * FROM games
	WHERE status = “OPEN”

Certes, DynamoDB peut filtrer les résultats sur une opération de requête ou de recherche, mais DynamoDB ne fonctionne pas comme une base de données relationnelle. Un filtre DynamoDB s’applique après l’extraction des éléments initiaux correspondant à l’opération de requête ou de recherche. Le filtre réduit la taille de la charge utile envoyée depuis le service DynamoDB. Cependant, le nombre d’éléments initialement extraits est fonction des limites de tailles applicables à DynamoDB.

Fort heureusement, il existe des voies et moyens d’autoriser le filtrage des requêtes sur votre ensemble de données dans DynamoDB. Pour fournir des filtres efficaces sur votre table DynamoDB, vous devez les planifier dès le départ dans le modèle de données de votre table. Gardez à l’esprit les leçons apprises dans le deuxième module de l’atelier : passer en revue vos modèles d’accès, puis concevoir votre table.

Dans les étapes ci-dessous, nous allons utiliser un index secondaire global pour rechercher les jeux ouverts. De manière précise, nous allons utiliser la technique de l’index épars pour gérer ce modèle d’accès.

Durée du module : 40 minutes


  • Étape 1 : modéliser un index secondaire épars

    Les index secondaires sont des outils essentiels de modélisation des données dans DynamoDB. Ils vous permettent de façonner vos données afin d’autoriser des modèles de requêtes alternatifs. Pour créer un index secondaire, vous devez spécifier la clé principale de l’index, exactement comme vous avez procédé précédemment pour créer une table. Veuillez noter que la clé principale d’un index secondaire global ne doit pas nécessairement être unique pour chaque élément. DynamoDB copie ensuite les éléments dans un index en fonction des attributs spécifiés. Vous pouvez donc interroger l’index exactement comme vous le faites avec votre table.

    L’utilisation des index secondaires épars est une stratégie de pointe dans DynamoDB. Avec les index secondaires, DynamoDB ne copie les éléments depuis la table initiale que s’ils comportent les composants de la clé principale dans l’index secondaire. Les éléments qui ne comportent pas les composants de la clé principale ne sont pas copiés, d’où l’appellation index secondaires « épars ».

    Examinons à présent comment cela se déroule dans notre cas. Rappelez-vous que nous disposons de deux modèles d’accès pour rechercher des jeux ouverts :

    • Rechercher des jeux ouverts (Lecture)
    • Rechercher des jeux ouverts par carte (Lecture)

    Nous pouvons créer un index secondaire en utilisant une clé principale composite dans laquelle la clé HASH est l’attribut de la carte pour le jeu, et la clé RANGE l’attribut open_timestamp pour le jeu, servant à indiquer l’heure d’ouverture du jeu.

    L’aspect important pour nous est le suivant : lorsque le jeu est plein, l’attribut open_timestamp est effacé. Lorsque l’attribut est effacé, le jeu plein est supprimé de l’index secondaire, car n’ayant pas de valeur pour l’attribut de la clé RANGE. C’est ce qui maintient notre index épars : il inclut uniquement les jeux ouverts ayant l’attribut open_timestamp.

    Dans la prochaine étape, nous allons créer l’index secondaire.

  • Étape 2 : créer un index secondaire épars

    Dans cette étape, nous créons l’index secondaire épars pour les jeux ouverts (c’est-à-dire ceux qui ne sont pas encore pleins).

    La création d’un index secondaire est similaire à celle d’une table. Le code que vous avez téléchargé comporte un fichier de script dans le répertoire scripts/, dénommé add_secondary_index.py. Le contenu de ce fichier est le suivant :

    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)

    Chaque fois que vous utilisez des attributs dans une clé principale pour une table ou un index secondaire, ils doivent être définis dans AttributeDefinitions. Nous créons ensuite un nouvel index secondaire dans la propriété GlobalSecondaryIndexUpdates. Pour cet index secondaire, nous spécifions le nom d’index, le schéma de la clé principale, le débit alloué et les attributs désirés pour le projet.

    Veuillez noter que nous n’avons pas eu à indiquer que notre index secondaire est destiné à être utilisé comme index épars. Il s’agit simplement là d’une fonction des données que vous incorporez. Si vous écrivez vers votre table des éléments n’ayant pas les attributs pour vos index secondaires, ces éléments ne seront pas inclus dans votre index secondaire.

    Créez votre index secondaire en exécutant la commande ci-dessous.

    python scripts/add_secondary_index.py

    Vous obtiendrez le message suivant dans la console : « Table updated successfully. » (La table a été mise à jour.)

    Dans la prochaine étape, nous utilisons l’index épars pour effectuer une recherche par carte des jeux ouverts.

  • Étape 3 : interroger l’index secondaire épars

    À présent que nous avons configuré l’index secondaire, utilisons-le pour répondre à certains modèles d’accès.

    Pour utiliser un index secondaire, deux appels d’API sont disponibles : Requête et Recherche. Avec l’API Requête, vous devez spécifier la clé HASH, et elle renvoie le résultat ciblé. Avec l’API Recherche, vous ne spécifiez pas de clé HASH, et l’opération s’exécute sur votre table tout entière. Sauf cas rares, les API Recherche sont déconseillées dans DynamoDB, car elles accèdent à tous les éléments dans votre base de données. Si votre table comporte une importante quantité de données, leur recherche peut être très longue. Dans la prochaine étape, nous allons vous montrer pourquoi les API Recherche peuvent être très utiles en cas d’utilisation conjointe avec les index épars.

    Nous pouvons utiliser l’API Requête dans l’index secondaire que nous avons créé à l’étape précédente pour effectuer une recherche par nom de carte de tous les jeux ouverts. L’index secondaire est partitionné par nom de carte, ce qui nous permet d’envoyer des requêtes ciblées pour rechercher les jeux ouverts.

    Le code que vous avez téléchargé comporte un fichier find_open_games_by_map.py inclus dans le répertoire application/. Le contenu de ce script est le suivant :

    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)

    Dans le script précédent, la fonction find_open_games_by_map est similaire à une fonction que vous auriez dans votre application. La fonction accepte un nom de carte et interroge OpenGamesIndex pour rechercher tous les jeux ouverts pour une carte donnée. Elle regroupe ensuite les entités renvoyées dans les objets jeu, qui peuvent être utilisés dans votre application.

    Exécutez ce script en exécutant la commande suivante dans votre terminal :

    python application/find_open_games_by_map.py

    Le terminal affichera la sortie ci-dessous avec quatre jeux ouverts pour la carte 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/

    Dans l’étape suivante, nous allons utiliser l’API Recherche pour effectuer une recherche dans l’index secondaire épars.

  • Étape 4 : effectuer une recherche dans l’index secondaire épars

    Dans l’étape précédente, nous avons appris comment rechercher des jeux pour une carte donnée. Cette opération est très utile, car certains joueurs préfèrent parfois jouer une carte particulière. D’autres joueurs peuvent être disposés à jouer à un jeu, quelle que soit la carte. Dans cette section, nous allons vous apprendre comment rechercher n’importe quel jeu dans l’application, indépendamment du type de carte. Pour ce faire, nous utilisons l’API Recherche.

    En général, vous ne souhaitez pas concevoir votre table pour qu’elle utilise l’opération Recherche de DynamoDB, car DynamoDB est conçu pour les requêtes de précision qui recueillent exactement les entités dont vous avez besoin. Une opération Recherche capture un ensemble aléatoire d’entités dans votre table. De ce fait, trouver les entités dont vous avez besoin peut nécessiter plusieurs allers-retours dans la base de données.

    Toutefois, l’opération Recherche est parfois être très utile. Dans notre cas, nous avons un index secondaire épars, ce qui signifie qu’il ne devrait pas contenir une multiplicité d’entités. De plus, l’index inclut uniquement les jeux ouverts, qui constituent l’objet même de notre recherche.

    Pour ce cas d’utilisation, l’opération Recherche est parfaitement indiquée. Examinons son fonctionnement. Le code que vous avez téléchargé comporte un fichier find_open_games.py inclus dans le répertoire application/. Le contenu du fichier est le suivant :

    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)

    Ce code est similaire à celui de l’étape précédente. Cependant, plutôt que d’utiliser la méthode query() sur le client DynamoDB, nous allons utiliser la méthode scan(). Étant donné que nous utilisons l’approche scan(), nous n’avons besoin de rien spécifier concernant les conditions des clés, comme cela a été le cas avec query(). DynamoDB renvoie simplement une collection aléatoire d’éléments.

    Exécutez le script avec la commande suivante dans votre terminal.

    python application/find_open_games.py

    Votre terminal devrait imprimer une liste de neuf jeux ouverts sur une variété de cartes.

    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>
    

    Dans cette étape, nous avons vu que l’utilisation de l’opération Recherche peut être la solution idéale dans certaines circonstances bien précises. Nous avons utilisé Recherche pour recueillir une collection d’entités dans notre index secondaire épars afin d’indiquer aux joueurs les jeux ouverts.

    Dans les étapes suivantes, nous allons satisfaire aux deux modèles d’accès :

    • Rejoindre un jeu pour un utilisateur (Écriture)
    • Démarrer un jeu (Écriture)

    Pour satisfaire au modèle d’accès « Rejoindre un jeu pour un utilisateur » dans les étapes ci-dessous, nous allons utiliser les transactions DynamoDB. Les transactions sont très prisées dans les systèmes relationnels pour les opérations qui affectent plusieurs éléments de données à la fois. Par exemple, imaginez que vous gériez une banque. Le client Alejandra transfère 100 USD au client Ana. Lors de l’enregistrement de cette transaction, vous devrez utiliser une transaction pour vous assurer que les modifications s’appliquent au solde de chacun des deux clients, et non à celui d’un seul.

    Les transactions DynamoDB simplifient la création d’applications qui appliquent des modifications à plusieurs éléments dans le cadre d’une opération unique. Avec les transactions, vous pouvez agir sur un maximum de 10 éléments dans le cadre d’une seule requête de transaction.

    Dans un appel d’API TransactWriteItem, vous pouvez utiliser les opérations ci-dessous.

    • Put : permet d’insérer ou d’écraser un élément.
    • Update : permet de mettre à jour un élément existant.
    • Delete : permet de supprimer un élément.
    • ConditionCheck : permet d’affirmer une condition sur un élément existant sans le modifier.

     

    Dans l’étape suivante, nous allons utiliser une transaction DynamoDB lors de l’ajout de nouveaux utilisateurs à un jeu, tout en évitant le surremplissage du jeu.

  • Étape 5 : ajouter des utilisateurs à un jeu

    Le premier modèle d’accès que nous avons abordé dans ce module est l’ajout de nouveaux utilisateurs à un jeu.

    Lors de l’ajout d’un nouvel utilisateur à un jeu, nous devons veiller aux conditions ci-dessous :

    • confirmer que le nombre de joueurs sur le jeu est inférieur à 50 (chaque jeu peut compter au plus 50 joueurs) ;
    • confirmer que l’utilisateur à ajouter n’est pas encore sur le jeu ;
    • créer une nouvelle entité UserGameMapping pour ajouter l’utiliser au jeu ;
    • incrémenter l’attribut nombre d’utilisateurs sur l’entité jeu afin d’effectuer le suivi du nombre de joueurs présents sur le jeu.

    Veuillez noter que la réalisation de toutes ces conditions passe par des actions d’écriture sur l’entité jeu existante et la nouvelle entité UserGameMapping, mais aussi la mise en place d’une logique conditionnelle pour chacune des entités. Voilà donc le type d’opération qui convient parfaitement aux transactions DynamoDB, car vous devez travailler sur plusieurs entités dans le cadre d’une seule et même requête, qui doit ou réussir ou échouer intégralement.

    Le code que vous avez téléchargé comporte un script join_game.py inclus dans le répertoire application/. La fonction contenue dans ce script utilise une transaction DynamoDB pour ajouter un utilisateur à un jeu.

    Le contenu du script est le suivant :

    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)

    Dans la fonction join_game_for_user de ce script, la méthode transact_write_items() effectue une transaction d’écriture. Cette transaction comprend deux opérations.

    Dans la première opération, nous utilisons une opération Put pour insérer une nouvelle entité UserGameMapping. Pour cette opération, nous spécifions une condition selon laquelle l’attribut SK ne doit pas exister pour cette entité. Ainsi, une entité avec les attributs PK et SK n’existe pas encore. Si une telle entité préexistait, cela signifierait que l’utilisateur a déjà rejoint le jeu.

    La seconde opération est une opération Update sur l’entité jeu visant à incrémenter de 1 l’attribut nombre d’utilisateurs. Dans le cadre de cette opération, nous ajoutons une vérification conditionnelle selon laquelle la valeur actuelle de l’attribut nombre d’utilisateurs ne doit pas être supérieure à 50. Dès que 50 personnes rejoignent le jeu, il est plein et prêt à démarrer.

    Exécutez ce script avec la commande suivante dans votre terminal.

    python application/join_game.py

    La sortie obtenue dans votre terminal doit indiquer que l’utilisateur a été ajouté au jeu.

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

    Veuillez noter que si vous essayez d’exécuter à nouveau le script, la fonction échouera. L’utilisateur vlopez a déjà été ajouté au jeu. Ainsi, toute nouvelle tentative d’ajout dudit utilisateur ne satisfait pas aux conditions que nous avons énoncées.

    L’ajout des transactions DynamoDB simplifie considérablement le flux de travail des opérations complexes comme celles-ci. Sans les transactions, ces opérations auraient nécessité de nombreux appels d’API avec des conditions complexes et des retours arrière manuels en cas de conflit. Désormais, nous pouvons implémenter ces opérations complexes avec moins de 50 lignes de code.

    Dans l’étape suivante, nous allons aborder le modèle d’accès « Démarrer un jeu (Écriture) ».

  • Étape 6 : démarrer un jeu

    Dès qu’un jeu compte 50 utilisateurs, son créateur peut lancer la partie. Dans cette étape, nous allons expliquer comment gérer ce modèle d’accès.

    Lorsque notre application backend reçoit une requête pour démarrer un jeu, nous vérifions trois facteurs :

    • Le jeu compte 50 utilisateurs connectés.
    • L’utilisateur auteur de la requête est le créateur du jeu.
    • Le jeu n’a pas encore démarré.

    Nous pouvons gérer chacune de ces vérifications dans une expression de condition dans une requête visant à mettre à jour le jeu. Si les vérifications aboutissent toutes, nous devrons actualiser notre entité comme suit :

    • Supprimez l’attribut open_timestamp afin qu’il n’apparaisse pas comme un jeu ouvert dans l’index secondaire épars étudié dans le module précédent.
    • Ajoutez un attribut start_time pour indiquer l’heure de début du jeu.

    Le code que vous avez téléchargé comporte un script start_game.py inclus dans le répertoire application/. Le contenu du fichier est le suivant :

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

    Dans ce script, la fonction start_game est similaire à celle que vous auriez dans votre application. La fonction utilise les attributs game_id, requesting_user et start_time, et exécute une requête pour mettre à jour l’entité jeu afin de lancer le jeu.

    Le paramètre ConditionExpression de l’appel update_item() spécifie chacune des trois vérifications listées précédemment dans cette étape : le jeu doit compter 50 utilisateurs, l’utilisateur demandant le début du jeu doit être le créateur du jeu, et le jeu ne peut pas avoir un attribut start_time, car cela indiquerait qu’il a déjà démarré.

    Dans le paramètre UpdateExpression, vous pouvez voir les modifications que nous souhaitons apporter à notre entité. Pour commencer, nous supprimons l’attribut open_timestamp dans l’entité, puis nous réglons l’attribut start_time sur l’heure de début du jeu.

    Exécutez ce script dans votre terminal avec la commande suivante.

    python application/start_game.py

    Vous devrez obtenir une sortie dans votre terminal indiquant que le jeu a bien démarré.

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

    Essayez d’exécuter le script une nouvelle fois dans votre terminal. Cette fois-ci, vous devrez obtenir un message d’erreur indiquant que vous ne pouvez pas démarrer le jeu. La raison en est que vous avez déjà démarré le jeu, et par conséquent l’attribut start_time existe. Ainsi, la requête a échoué la vérification de condition de l’entité.

    Rappelons qu’il existe une relation de plusieurs à plusieurs entre l’entité jeu et les entités utilisateurs associées, et que les relations sont représentées par une entité UserGameMapping.

    Parfois, vous pouvez souhaiter interroger les deux parties d’une relation. À partir de la configuration de notre clé principale, nous pouvons rechercher toutes les entités utilisateurs dans un jeu. Nous pouvons activer l’interrogation de toutes les entités jeux en utilisant un index inversé.

    Dans DynamoDB, un index inversé est un index secondaire qui est l’inverse de votre clé principale. La clé RANGE devient ainsi votre clé HASH, et vice versa. Ce modèle renverse votre table et vous permet d’envoyer des requêtes sur l’autre partie de vos relations de plusieurs à plusieurs.

    Dans les étapes qui suivent, nous allons ajouter un index inversé à la table et expliquer comment l’utiliser pour extraire toutes les entités jeux pour un utilisateur donné. 

  • Étape 7 : ajouter un index inversé

    Dans cette étape, nous allons ajouter un index inversé à la table. Un index inversé se crée comme tout autre index secondaire.

    Le code que vous avez téléchargé comporte un script add_inverted_index.py dans le répertoire scripts/. Ce script Python ajoute un index inversé à votre table.

    Le contenu de ce fichier est le suivant :

    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)
    

    Dans ce script, nous appelons une méthode update_table()sur notre client DynamoDB. Dans le cadre de cette méthode, nous intégrons dans l’index les informations sur l’index secondaire que nous souhaitons créer, notamment le schéma de la clé pour l’index, le débit alloué et les attributs du projet.

    Exécutez le script en saisissant la commande suivante dans votre terminal.

    python scripts/add_inverted_index.py

    Votre terminal affichera une sortie indiquant que votre index a bien été créé.

    Table updated successfully.

    Dans l’étape suivante, nous allons utiliser notre index inversé pour extraire toutes les entités jeux pour un utilisateur donné.

  • Étape 8 : extraire des jeux pour un utilisateur

    Maintenant que nous avons créé notre index inversé, utilisons-le pour extraire les entités jeux jouées par un utilisateur. Pour ce faire, nous devons interroger l’index inversé à l’aide de l’utilisateur dont nous souhaitons afficher les entités jeux.

    Le code que vous avez téléchargé comporte un script sfind_games_for_user.py inclus dans le répertoire application/. Le contenu du fichier est le suivant :

    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)
    

    Ce script comporte une fonction dénommée find_games_for_user(), qui est similaire à une fonction que vous auriez dans votre jeu. Cette fonction part d’un nom d’utilisateur pour renvoyer tous les jeux joués par l’utilisateur en question.

    Exécutez le script dans votre terminal avec la commande suivante.

    python application/find_games_for_user.py

    Le script devrait imprimer tous les jeux joués par l’utilisateur 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>
    

    Dans ce module, nous avons ajouté un index secondaire à la table. Cette démarche répondait à deux modèles d’accès supplémentaires :

    • Rechercher des jeux ouverts par carte (Lecture)
    • Rechercher des jeux ouverts (Lecture)

    Pour y parvenir, nous avons utilisé un index épars incluant uniquement les jeux encore ouverts pour d’autres joueurs. Nous avons ensuite utilisé les API Requête et Recherche sur les index pour rechercher les jeux ouverts.

    Nous avons également appris comment satisfaire aux deux opérations d’écriture de pointe dans l’application. Premièrement, nous avons utilisé les transactions DynamoDB lorsqu’un utilisateur rejoignait un jeu. Grâce aux transactions, nous avons pris en charge une écriture conditionnelle complexe sur plusieurs entités dans une seule requête.

    Deuxièmement, nous avons implémenté la fonction permettant au créateur d’un jeu de lancer une partie lorsqu’elle est prête. Dans ce modèle d’accès, nous avons eu une opération de mise à jour qui nécessitait la vérification de la valeur de trois attributs et l’actualisation de deux attributs. Vous pouvez exprimer cette logique complexe dans une seule requête en vous appuyant sur la puissance des paramètres expression de condition et mise à jour d’expression.

    Troisièmement, nous avons satisfait au modèle d’accès final en procédant à l’extraction de toutes les entités jeux jouées par un utilisateur. Pour prendre en charge ce modèle d’accès, nous avons créé un index secondaire à l’aide du modèle d’index inversé afin de permettre l’interrogation de l’autre partie des relations de plusieurs à plusieurs qui existent entre les entités utilisateurs et jeux.

    Dans le prochain module, nous allons nettoyer les ressources que nous avons créées.