Étape 2 : examen du modèle de données et des détails de la mise en œuvre - Amazon DynamoDB

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Étape 2 : examen du modèle de données et des détails de la mise en œuvre

2.1 : modèle de données de base

Cet exemple d'application met en évidence les concepts de modèle de données DynamoDB suivants :

  • Table – Dans DynamoDB, une table est un ensemble d'éléments (registres), et chaque élément est un ensemble de paires nom-valeur appelées attributs.

    Dans cet Tic-Tac-Toe exemple, l'application stocke toutes les données du jeu dans un tableau,Games. L'application crée un élément par jeu dans la table et stocke toutes les données de jeux en tant qu'attributs. Une tic-tac-toe partie peut comporter jusqu'à neuf coups. Les tables DynamoDB n'ayant pas de schéma dans les cas où seule la clé primaire est l'attribut obligatoire, l'application peut stocker différents nombres d'attributs par élément de jeu.

    La table Games a une clé primaire simple composée d'un attribut, GameId, de type chaîne. L'application attribue un ID unique à chaque partie. Pour plus d'informations sur les clés primaires DynamoDB, consultez Clé primaire.

    Lorsqu'un utilisateur lance un tic-tac-toe jeu en invitant un autre utilisateur à jouer, l'application crée un nouvel élément dans le Games tableau avec des attributs stockant les métadonnées du jeu, tels que les suivants :

    • HostId, l'utilisateur qui a lancé la partie.

    • Opponent, l'utilisateur qui a été invité à jouer.

    • L'utilisateur dont c'est le tour de jouer. L'utilisateur qui a lancé la partie joue en premier.

    • L'utilisateur qui utilise le symbole O sur le plateau. L'utilisateur qui initie les parties utilise le symbole O.

    En outre, l'application crée un attribut concaténé StatusDate, marquant l'état du jeu initial en tant que PENDING. La capture d'écran suivante présente un exemple d'élément tel qu'il s'affiche dans la console DynamoDB :

    Capture d'écran de la console de la table des attributs.

    En cours de partie, l'application ajoute un attribut à la table pour chaque déplacement. Le nom d'attribut est la position sur le plateau, par exemple TopLeft ou BottomRight. Par exemple, un déplacement peut avoir un attribut TopLeft avec la valeur O, un attribut TopRight avec la valeur O et un attribut BottomRight avec la valeur X. La valeur d'attribut est O ou X, selon l'utilisateur qui a effectué le déplacement. Par exemple, considérez le plateau suivant.

    Capture d'écran montrant une tic-tac-toe partie terminée qui s'est terminée par une égalité.
  • Attributs de valeur concaténée – L'attribut StatusDate illustre un attribut de valeur concaténée. Dans cette approche, au lieu de créer des attributs distincts pour stocker le statut du jeu (PENDING, IN_PROGRESS et FINISHED) et la date (moment du dernier déplacement), vous les associez en un seul attribut, par exemple IN_PROGRESS_2014-04-30 10:20:32.

    L'application utilise ensuite l'attribut StatusDate pour la création d'index secondaires en spécifiant StatusDate comme clé de tri pour l'index. L'avantage d'utiliser l'attribut de valeur concaténée StatusDate est illustré plus en détails dans les index abordés par la suite.

  • Index secondaires globaux – Vous pouvez utiliser la clé primaire de la table, GameId, pour interroger efficacement la table afin de trouver un élément de jeu. Pour interroger la table sur des attributs autres que les attributs de clé primaires, DynamoDB prend en charge la création d'index secondaires. Dans cet exemple d'application, vous créez les deux index secondaires suivants :

    Capture d'écran montrant les index secondaires oppStatusDate globaux hostStatusDate et globaux créés dans l'exemple d'application.
    • HostId- StatusDate -indice. Cet index a HostId comme clé de partition et StatusDate comme une clé de tri. Vous pouvez utiliser cet index pour interroger sur HostId, par exemple pour trouver des jeux hébergés par un utilisateur particulier.

    • OpponentId- StatusDate -indice. Cet index a OpponentId comme clé de partition et StatusDate comme une clé de tri. Vous pouvez utiliser cet index pour interroger sur Opponent, par exemple pour trouver des jeux dans lesquels un utilisateur particulier est l'adversaire.

    Ces index sont appelés index secondaires globaux, car la clé de partition dans ces index n'est pas la même que la clé de partition (GameId) utilisée dans la clé primaire de la table.

    Notez que les deux index spécifient StatusDate comme clé de tri. Cela permet d'effectuer les opérations suivantes :

    • Vous pouvez interroger à l'aide de l'opérateur de comparaison BEGINS_WITH. Par exemple, vous pouvez trouver tous les jeux avec l'attribut IN_PROGRESS hébergés par un utilisateur particulier. Dans ce cas, l'opérateur BEGINS_WITH vérifie la valeur StatusDate qui commence par IN_PROGRESS.

    • DynamoDB stocke les éléments dans l'index dans l'ordre, par valeur de clé de tri. Ainsi, si tous les préfixes de statut sont identiques (par exemple,IN_PROGRESS), le ISO format utilisé pour la partie date comportera des éléments triés du plus ancien au plus récent. Cette approche permet d'effectuer certaines requêtes efficacement, par exemple les éléments suivants :

      • Récupérez jusqu'à 10 des jeux IN_PROGRESS les plus récents organisés par l'utilisateur qui est connecté. Pour cette requête, vous spécifiez l'index HostId-StatusDate-index.

      • Récupérez jusqu'à 10 des jeux IN_PROGRESS les plus récents où l'utilisateur connecté est l'adversaire. Pour cette requête, vous spécifiez l'index OpponentId-StatusDate-index.

Pour plus d'informations sur le fonctionnement des index secondaires, consultez Améliorer l'accès aux données grâce aux index secondaires dans DynamoDB.

2.2 : application en action (procédure pas à pas de code)

Cette application a deux pages principales :

  • Page d'accueil — Cette page fournit à l'utilisateur un identifiant simple, un CREATEbouton pour créer un nouveau tic-tac-toe jeu, une liste des parties en cours, un historique des parties et toutes les invitations à des jeux en attente.

    La page d'accueil n'est pas actualisée automatiquement. Vous devez actualiser la page pour actualiser les listes.

  • Page de jeu — Cette page montre la tic-tac-toe grille sur laquelle les utilisateurs jouent.

    L'application met à jour la page de jeu automatiquement chaque seconde. JavaScript Dans votre navigateur, le serveur Web Python appelle le serveur Web Python toutes les secondes pour demander à la table Games si les éléments du jeu de la table ont changé. Si tel est le cas, JavaScript déclenche une actualisation de la page afin que l'utilisateur puisse voir le tableau mis à jour.

Nous verrons en détails le mode de fonctionnement de l'application.

Page d'accueil

Une fois l'utilisateur connecté, l'application affiche les trois listes d'informations suivantes :

Capture d'écran montrant la page d'accueil de l'application avec 3 listes : invitations en attente, parties en cours et historique récent.
  • Invitations – Cette liste affiche jusqu'aux 10 dernières invitations d'autres personnes, qui sont en attente d'acceptation par l'utilisateur connecté. Dans la capture d'écran précédente, user1 dispose d'invitations d'user5 et d'user2 en attente.

  • Games in-progress – Cette liste affiche jusqu'aux 10 dernière parties en cours. Il s'agit de parties auxquelles l'utilisateur participe activement et qui ont le statut IN_PROGRESS. Sur la capture d'écran, l'utilisateur1 joue activement à un tic-tac-toe jeu avec utilisateur3 et utilisateur4.

  • Recent history – Cette liste affiche jusqu'aux 10 dernières parties que l'utilisateur a terminées, dont le statut est FINISHED. Dans la partie illustrée dans la capture d'écran, user1 a joué avec user2. Pour chaque partie terminée, la liste présente le résultat.

Dans le code, la fonction index (dans application.py) effectue les trois appels suivants pour récupérer des informations sur le statut du jeu :

inviteGames = controller.getGameInvites(session["username"]) inProgressGames = controller.getGamesWithStatus(session["username"], "IN_PROGRESS") finishedGames = controller.getGamesWithStatus(session["username"], "FINISHED")

Chacun de ces appels renvoie une liste d'éléments de DynamoDB qui sont encapsulés par les objets Game. Il est facile d'extraire des données de ces objets dans la vue. La fonction d'index transmet ces listes d'objets à la vue pour qu'elle affiche leHTML.

return render_template("index.html", user=session["username"], invites=inviteGames, inprogress=inProgressGames, finished=finishedGames)

L' Tic-Tac-Toeapplication définit la Game classe principalement pour stocker les données de jeu extraites de DynamoDB. Ces fonctions renvoient des listes d'objets Game qui vous permettent d'isoler le reste de l'application du code relatif aux éléments Amazon DynamoDB. Par conséquent, ces fonctions vous aident à découpler le code de votre application à partir des détails de la couche de stockage de données.

Le modèle architectural décrit ici est également appelé modèle d'interface utilisateur model-view-controller (MVC). Dans ce cas, les instances de l'Gameobjet (représentant les données) sont le modèle et la HTML page est la vue. Le contrôleur est divisé en deux fichiers. Le fichier application.py a la logique de contrôleur pour la structure Flask, et la logique métier est isolée dans le fichier gameController.py. C'est-à-dire que l'application stocke tout ce qui a trait à SDK DynamoDB dans son propre fichier séparé dans le dossier. dynamodb

Permettez-nous de passer en revue les trois fonctions et comment elles interrogent la table Games à l'aide des index secondaires globaux pour récupérer les données concernés.

Utilisation getGameInvites pour obtenir la liste des invitations à des jeux en attente

La fonction getGameInvites récupère la liste des 10 dernières invitations en suspens. Ces jeux ont été créés par des utilisateurs, mais les adversaires n'ont pas accepté les invitations à jouer. Pour ces jeux, l'état reste PENDING jusqu'à ce que l'adversaire accepte l'invitation. Si l'adversaire décline l'invitation, l'application supprime l'élément correspondant de la table.

La fonction spécifie la requête comme suit :

  • Elle spécifie l'index OpponentId-StatusDate-index à utiliser avec les valeurs de clés d'index et les opérateurs de comparaison suivants :

    • La clé de partition est OpponentId et prend la clé d'index user ID.

    • La clé de tri est StatusDate et prend l'opérateur de comparaison et la valeur de clé d'index beginswith="PENDING_".

    Vous utilisez l'index OpponentId-StatusDate-index pour récupérer les jeux auxquels l'utilisateur connecté est invité, c'est-à-dire où l'utilisateur connecté est l'adversaire.

  • La requête limite le résultat à 10 éléments.

gameInvitesIndex = self.cm.getGamesTable().query( Opponent__eq=user, StatusDate__beginswith="PENDING_", index="OpponentId-StatusDate-index", limit=10)

Dans l'index, pour chaque OpponentId (clé de partition), DynamoDB conserve les éléments triés par StatusDate (clé de tri). Par conséquent, les parties que la requête renvoie seront les 10 parties les plus récentes.

Utiliser getGamesWith le statut pour obtenir la liste des jeux ayant un statut spécifique

Une fois qu'un adversaire accepte une invitation, le statut du jeu passe sur IN_PROGRESS. Une fois que le jeu se termine, le statut passe sur FINISHED.

Les requêtes pour trouver des jeux qui sont en cours ou terminés sont identiques, à l'exception de la valeur du statut qui est différente. Par conséquent, l'application définit la fonction getGamesWithStatus, qui prend la valeur de statut comme paramètre.

inProgressGames = controller.getGamesWithStatus(session["username"], "IN_PROGRESS") finishedGames = controller.getGamesWithStatus(session["username"], "FINISHED")

La section suivante décrit les jeux en cours, mais la même description s'applique également aux jeux terminés.

Une liste de jeux en cours pour un utilisateur donné comprend les deux éléments suivants :

  • Jeux en cours hébergés par l'utilisateur

  • Jeux en cours où l'utilisateur est l'adversaire

La fonction getGamesWithStatus exécute les deux requêtes suivantes, chaque fois à l'aide de l'index secondaire approprié.

  • La fonction interroge la table Games en utilisant l'index HostId-StatusDate-index. Pour l'index, la requête spécifie les valeurs de clés primaires, clé de partition (HostId) et clé de tri (StatusDate), ainsi que les opérateurs de comparaison.

    hostGamesInProgress = self.cm.getGamesTable ().query(HostId__eq=user, StatusDate__beginswith=status, index="HostId-StatusDate-index", limit=10)

    Notez la syntaxe Python pour les opérateurs de comparaison :

    • HostId__eq=user spécifie l'opérateur de comparaison d'égalité.

    • StatusDate__beginswith=status spécifie l'opérateur de comparaison BEGINS_WITH.

  • La fonction interroge la table Games en utilisant l'index OpponentId-StatusDate-index.

    oppGamesInProgress = self.cm.getGamesTable().query(Opponent__eq=user, StatusDate__beginswith=status, index="OpponentId-StatusDate-index", limit=10)
  • Ensuite, la fonction combine les deux listes, trie et pour les premiers 0 à 10 éléments, elle crée une liste des objets Game et renvoie la liste à la fonction d'appel (à savoir, l'index).

    games = self.mergeQueries(hostGamesInProgress, oppGamesInProgress) return games

Page de jeu

La page de jeu est l'endroit où l'utilisateur joue à tic-tac-toe des jeux. Elle présente la grille de jeu ainsi que des informations relatives au jeu. La capture d'écran suivante illustre un exemple de jeu en cours :

Capture d'écran montrant un tic-tac-toe jeu en cours.

L'application affiche la page de jeu dans les cas suivants :

  • L'utilisateur crée un jeu en invitant un autre utilisateur à jouer.

    Dans ce cas, la page affiche l'utilisateur comme hôte et le statut de jeu comme PENDING, en attendant que l'adversaire accepte.

  • L'utilisateur accepte l'une des invitations en attente sur la page d'accueil.

    Dans ce cas, la page affiche l'utilisateur comme adversaire et le statut du jeu comme IN_PROGRESS.

Une sélection d'utilisateur sur le plateau génère une demande POST de formulaire à l'application. C'est-à-dire que Flask appelle la selectSquare fonction (inapplication.py) avec les données du HTML formulaire. Cette fonction, à son tour, appelle la fonction updateBoardAndTurn (dans gameController.py) pour mettre à jour l'élément de jeu comme suit :

  • Elle ajoute un nouvel attribut spécifique au déplacement.

  • Elle met à jour la valeur d'attribut Turn pour l'utilisateur qui jouera le prochain coup.

controller.updateBoardAndTurn(item, value, session["username"])

La fonction renvoie la valeur true si la mise à jour de l'élément a réussi ; sinon, elle renvoie la valeur false. Notez les éléments suivants sur la fonction updateBoardAndTurn :

  • La fonction appelle la update_item fonction SDK for Python pour effectuer un ensemble fini de mises à jour d'un élément existant. La fonction mappe à l'opération UpdateItem dans DynamoDB. Pour plus d'informations, consultez UpdateItem.

    Note

    La différence entre les opérations UpdateItemet PutItem est que PutItem remplace l'élément entier. Pour plus d'informations, consultez PutItem.

Pour l'appel update_item, le code identifie les éléments suivants :

  • La clé primaire de la table Games (à savoir, ItemId).

    key = { "GameId" : { "S" : gameId } }
  • Le nouvel attribut à ajouter, spécifique au déplacement de l'utilisateur en cours et sa valeur (par exemple, TopLeft="X").

    attributeUpdates = { position : { "Action" : "PUT", "Value" : { "S" : representation } } }
  • Conditions qui doivent avoir une valeur true pour que la mise à jour ait lieu :

    • La partie doit être en cours. C'est à dire que la valeur d'attribut StatusDate doit commencer par IN_PROGRESS.

    • Le tour actuel doit être un tour d'utilisateur valide comme indiqué par l'attribut Turn.

    • L'emplacement que l'utilisateur choisit doit être disponible. Autrement dit, l'attribut correspondant à l'emplacement ne doit pas exister.

    expectations = {"StatusDate" : {"AttributeValueList": [{"S" : "IN_PROGRESS_"}], "ComparisonOperator": "BEGINS_WITH"}, "Turn" : {"Value" : {"S" : current_player}}, position : {"Exists" : False}}

Maintenant, la fonction appelle update_item pour mettre à jour l'élément.

self.cm.db.update_item("Games", key=key, attribute_updates=attributeUpdates, expected=expectations)

Après le renvoi de la fonction, les appels de la fonction selectSquare sont redirigés, comme illustré dans l'exemple suivant.

redirect("/game="+gameId)

Cet appel permet au navigateur de s'actualiser. Dans le cadre de cette actualisation, l'application vérifie si la partie s'est terminée par une victoire ou un nul. Si c'est le cas, l'application met à jour l'élément de partie en conséquence.