

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
<a name="TicTacToe.Phase2"></a>

**Topics**
+ [2.1 : modèle de données de base](#TicTacToe.Phase2.DataModel)
+ [2.2 : application en action (procédure pas à pas de code)](#TicTacToe.Phase2.AppInAction)

## 2.1 : modèle de données de base
<a name="TicTacToe.Phase2.DataModel"></a>

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](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey). 

  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.](http://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/images/tic-tac-toe-10.png)

  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é.](http://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/images/tic-tac-toe-30.png)
+ ****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.](http://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/images/tic-tac-toe-indexes-10.png)
  + **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. Donc, si tous les préfixes de statut sont identiques (par exemple, `IN_PROGRESS`), le format ISO utilisé pour la partie date aura 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élioration de l’accès aux données avec les index secondaires dans DynamoDB](SecondaryIndexes.md).

## 2.2 : application en action (procédure pas à pas de code)
<a name="TicTacToe.Phase2.AppInAction"></a>

Cette application a deux pages principales :
+ ****Page d'accueil**** — Cette page fournit à l'utilisateur un identifiant simple, un bouton **CRÉER** 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
<a name="TicTacToe.Phase2.AppInAction.HomePage"></a>

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.](http://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/images/tic-tac-toe-homepage-10.png)

+ ****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 index passe ces listes d’objets à la vue pour afficher le HTML.

```
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 d’objet `Game` (qui représentent des données) sont le modèle et la page HTML 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`. Autrement dit, l’application stocke tout ce qui est associé au kit SDK DynamoDB dans son propre fichier 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
<a name="TicTacToe.Phase2.GameInAction.ListInvitations"></a>

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
<a name="TicTacToe.Phase2.GameInAction.ListGamesInProgressHistory"></a>

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
<a name="TicTacToe.Phase2.AppInAction.GamePage"></a>

La page de jeu est l'endroit où l'utilisateur joue tic-tac-toe. 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.](http://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/images/tic-tac-toe-example-board-10.png)


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. Autrement dit, Flask appelle la fonction `selectSquare` (dans `application.py`) avec les données de formulaire HTML. 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 fonction `update_item` du kit SDK pour Python afin d’apporter un ensemble fini de mises à jour à un élément existant. La fonction mappe à l’opération `UpdateItem` dans DynamoDB. Pour de plus amples informations, veuillez consulter [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html). 
**Note**  
La différence entre les opérations `UpdateItem`et `PutItem` est que `PutItem` remplace l’élément entier. Pour de plus amples informations, veuillez consulter [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html).

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. 