

# Etapa 2: examinar o modelo de dados e os detalhes da implantação
<a name="TicTacToe.Phase2"></a>

**Topics**
+ [2.1: modelo de dados básico](#TicTacToe.Phase2.DataModel)
+ [2.2: aplicação em ação (demonstração do código)](#TicTacToe.Phase2.AppInAction)

## 2.1: modelo de dados básico
<a name="TicTacToe.Phase2.DataModel"></a>

Esta aplicação de exemplo destaca os seguintes conceitos de modelo de dados do DynamoDB:
+ ****Tabela****: no DynamoDB, uma tabela é uma coleção de itens (ou seja, registros), e cada item é uma coleção de pares de nome-valor chamados atributos.

  Neste exemplo de Jogo da velha, o aplicativo armazena todos os dados do jogo em uma tabela, `Games`. O aplicativo cria um item na tabela por jogo e armazena todos os dados de jogos como atributos. Um jogo da velha pode ter até nove movimentações. Como as tabelas do DynamoDB não possuem um esquema em casos nos quais apenas a chave primária é o atributo obrigatório, a aplicação pode armazenar um número variável de atributos por item de jogo.

  A tabela `Games` possui uma chave primária simples composta de um atributo, `GameId`, do tipo string. O aplicativo atribui um ID exclusivo a cada jogo. Para obter mais informações sobre chaves primárias do DynamoDB, consulte [Chave primária](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey). 

  Quando um usuário inicia um jogo da velha, convidando outro usuário para jogar, o aplicativo cria um novo item na tabela `Games` com atributos que armazenam metadados de jogos, como os seguintes:
  + `HostId`, o usuário que iniciou o jogo.
  + `Opponent`, o usuário que foi convidado para jogar.
  + O usuário que está na vez de jogar. O usuário que iniciou o jogo joga primeiro.
  + O usuário que usa o símbolo **O** no quadro. O usuário que inicia os jogos usa o símbolo **O**.

  Além disso, o aplicativo cria um atributo `StatusDate` concatenado, marcando o estado inicial do jogo como `PENDING`. A captura de tela a seguir mostra um item de exemplo como ele aparece no console do DynamoDB:  
![Captura de tela do console da tabela de atributos.](http://docs.aws.amazon.com/pt_br/amazondynamodb/latest/developerguide/images/tic-tac-toe-10.png)

  À medida que o jogo progride, o aplicativo adiciona um atributo à tabela para cada movimento do jogo. O nome do atributo é a posição no quadro, por exemplo `TopLeft` ou `BottomRight`. Por exemplo, um movimento pode ter um atributo `TopLeft` com o valor `O`, um atributo `TopRight` com o valor `O` e um atributo `BottomRight` com o valor `X`. O valor do atributo é `O` ou `X`, dependendo de qual usuário fez o movimento. Por exemplo, considere o quadro a seguir.  
![Captura de tela mostrando um jogo da velha finalizado que terminou em um empate.](http://docs.aws.amazon.com/pt_br/amazondynamodb/latest/developerguide/images/tic-tac-toe-30.png)
+ ****Atributos de valor concatenados****: o atributo `StatusDate` ilustra um atributo de valor concatenado. Em essa abordagem, em vez de criar atributos separados para armazenar o status do jogo (`PENDING`, `IN_PROGRESS` e `FINISHED`) e a data (quando o último movimento foi feito), você pode combiná-los como um único atributo, por exemplo `IN_PROGRESS_2014-04-30 10:20:32`.

  Em seguida, o aplicativo usa o atributo `StatusDate` na criação de índices secundários, especificando `StatusDate` como uma chave de classificação para o índice. A vantagem de usar o atributo de valor concatenado `StatusDate` é melhor demonstrada nos índices discutidos a seguir.
+ ****Índices secundários globais****: você pode usar a chave primária da tabela, `GameId`, para consultar a tabela com eficiência para encontrar um item do jogo. Para possibilitar a consulta de outros atributos que não sejam atributos da chave primária na tabela, o DynamoDB oferece suporte à criação de índices secundários. Neste aplicativo de exemplo, você cria os seguintes dois índices secundários:   
![Captura de tela mostrando os índices secundários globais hostStatusDate e oppStatusDate criados no aplicativo de exemplo.](http://docs.aws.amazon.com/pt_br/amazondynamodb/latest/developerguide/images/tic-tac-toe-indexes-10.png)
  + **HostId-StatusDate-index**. O índice tem `HostId` como chave de partição e `StatusDate` como chave de classificação. Você pode usar esse índice para consultar o `HostId`, por exemplo, para localizar jogos hospedados por um determinado usuário. 
  + **OpponentId-StatusDate-index**. O índice tem `OpponentId` como chave de partição e `StatusDate` como chave de classificação. Você pode usar esse índice para consultar o `Opponent`, por exemplo, para localizar jogos nos quais um determinado usuário é o oponente.

  Esses índices são chamados de índices secundários globais porque a chave de partição nesses índices não é igual à chave de partição (`GameId`), usada na chave primária da tabela. 

  Observe que ambos os índices especificam `StatusDate` como chave de classificação. Fazer isso permite o seguinte:
  + Você pode consultar usando o operador de comparação `BEGINS_WITH`. Por exemplo, você pode encontrar todos os jogos com o atributo `IN_PROGRESS` hospedados por um determinado usuário. Neste caso, o operador `BEGINS_WITH` verifica o valor `StatusDate` que começa com `IN_PROGRESS`.
  + O DynamoDB armazena os itens no índice em ordem classificada, por valor de chave de classificação. Portanto, se todos os prefixos de status forem os mesmos (por exemplo, `IN_PROGRESS`), o formato ISO usado para a parte de data terá itens classificados do mais antigo para o mais recente. Essa abordagem permite que determinadas consultas sejam executadas de forma eficiente, por exemplo, a seguinte: 
    + Recupere até 10 dos jogos `IN_PROGRESS` mais recentes hospedados pelo usuário que está conectado. Para essa consulta, você especifica o índice `HostId-StatusDate-index`.
    + Recupere até 10 dos jogos `IN_PROGRESS` mais recentes nos quais o usuário conectado é o oponente. Para essa consulta, você especifica o índice `OpponentId-StatusDate-index`.

Para obter mais informações sobre índices secundários, consulte [Melhorar o acesso aos dados com índices secundários no DynamoDB](SecondaryIndexes.md).

## 2.2: aplicação em ação (demonstração do código)
<a name="TicTacToe.Phase2.AppInAction"></a>

Este aplicativo tem duas páginas principais:
+ ****Página inicial**** – esta página oferece ao usuário um login simples, um botão **CRIAR** para criar um novo jogo da velha, uma lista de jogos em andamento, o histórico de jogos e todos os convites de jogo pendentes ativos. 

  A página inicial não é atualizada automaticamente; você deve atualizar a página para atualizar as listas.
+ ****Página do jogo**** – Esta página mostra a grade do jogo da velha na qual os usuários jogam. 

  O aplicativo atualiza a página de jogos automaticamente a cada segundo. O JavaScript no navegador chama o servidor web Python a cada segundo para consultar na tabela Games se os itens do jogo na tabela foram alterados. Se for o caso, o JavaScript aciona uma atualização da página para que o usuário veja o quadro atualizado.

Vamos ver em detalhes como o aplicativo funciona.

### Página inicial
<a name="TicTacToe.Phase2.AppInAction.HomePage"></a>

Depois que o usuário fizer login, o aplicativo exibe as três listas de informações a seguir.

![Captura de tela mostrando a página inicial do aplicativo com 3 listas: convites pendentes, jogos em andamento e histórico recente.](http://docs.aws.amazon.com/pt_br/amazondynamodb/latest/developerguide/images/tic-tac-toe-homepage-10.png)

+ ****Convites****: esta lista mostra até 10 convites mais recentes de outros usuários que estão aguardando aceitação pelo usuário que está conectado. Na captura de tela anterior, o user1 tem convites de user5 e de user2 pendentes.
+ ****Jogos em andamento****: esta lista mostra até 10 jogos mais recentes que estão em andamento. Esses são os jogos que o usuário está ativamente jogando, que têm o status `IN_PROGRESS`. Na captura de tela, o user1 está jogando ativamente um jogo da velha com user3 e user4.
+ ****Histórico recente****: esta lista mostra até 10 jogos mais recentes que o usuário terminou, os quais tem o status `FINISHED`. No jogo apresentado na captura de tela, o user1 jogou anteriormente com o user2. Para cada jogo concluído, a lista mostra o resultado do jogo.

No código, a função `index` (em `application.py`) faz as seguintes três chamadas para recuperar informações de status do jogo:

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

Cada uma dessas chamadas retorna uma lista de itens do DynamoDB que são encapsulados por objetos `Game`. É fácil extrair dados desses objetos na exibição. A função de índice passa essas listas de objetos para a exibição para renderizar o HTML.

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

A aplicação Jogo da velha define a classe `Game` principalmente para armazenar os dados do jogo recuperados do DynamoDB. Essas funções retornam listas de objetos `Game` que permitem que você isole o resto da aplicação do código relacionado a itens do Amazon DynamoDB. Portanto, essas funções ajudam a separar o código do seu aplicativo dos detalhes da camada de armazenamento de dados. 

O padrão de arquitetura descrito aqui também é chamado de padrão de interface do usuário MVC (controlador de visualização de modelo). Neste caso, as instâncias do objeto `Game` (representando os dados) são o modelo, e a página HTML é a exibição. O controlador é dividido em dois arquivos. O arquivo `application.py` tem o controlador para o framework Flask, e a lógica de negócios é isolada no arquivo `gameController.py`. Ou seja, a aplicação armazena tudo o que tem a ver com o DynamoDB SDK em seu próprio arquivo separado na pasta `dynamodb`.

Vamos analisar as três funções e como elas consultam a tabela Games usando índices secundários globais para recuperar dados relevantes.

#### Usar getGameInvites para obter a lista de convites de jogo pendentes
<a name="TicTacToe.Phase2.GameInAction.ListInvitations"></a>

A função `getGameInvites` recupera a lista dos 10 convites pendentes mais recentes. Esses jogos foram criados pelos usuários, mas os oponentes não aceitaram os convites de jogo. Para esses jogos, o status permanece `PENDING` até que o oponente aceite o convite. Se o oponente recusar o convite, o aplicativo removerá o item correspondente da tabela. 

A função especifica a consulta da seguinte forma:
+ Ela especifica o índice `OpponentId-StatusDate-index` para ser usado com os seguintes valores de chave de índice e operadores de comparação:
  + A chave de partição é `OpponentId` e usa a chave de índice `{{user ID}}`.
  + A chave de classificação é `StatusDate` e usa o operador de comparação e o valor de chave de índice `beginswith="PENDING_"`.

  Você pode usar o índice `OpponentId-StatusDate-index` para recuperar jogos para os quais o usuário conectado é convidado – ou seja, nos quais o usuário conectado é o oponente.
+ A consulta limita o resultado a 10 itens.

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

No índice, para cada `OpponentId` (a chave de partição), o DynamoDB mantém itens classificados por `StatusDate` (a chave de classificação). Portanto, os jogos que a consulta retorna serão os 10 jogos mais recentes.

#### Usar getGamesWithStatus para obter a lista de jogos com um status específico
<a name="TicTacToe.Phase2.GameInAction.ListGamesInProgressHistory"></a>

Depois que um oponente aceita um convite de jogo, o status do jogo muda para `IN_PROGRESS`. Depois que o jogo for concluído, o status mudará para `FINISHED`. 

As consultas para encontrar jogos que estão em andamento ou concluídos são as mesmas, exceto para o valor de status diferente. Portanto, o aplicativo define a função `getGamesWithStatus`, que usa o valor de status como um parâmetro. 

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

A seção a seguir aborda os jogos em andamento, mas a mesma descrição também se aplica a jogos concluídos.

Uma lista de jogos em andamento para um determinado usuário inclui o seguinte: 
+ Jogos em andamento hospedados pelo usuário 
+ Jogos em andamento nos quais o usuário é o oponente 

A função `getGamesWithStatus` executa as duas consultas seguintes, cada vez usando o índice secundário apropriado. 
+ A função consulta a tabela `Games` usando o índice `HostId-StatusDate-index`. Para o índice, a consulta especifica valores de chave primária – tanto os valores de chave de partição (`HostId`) quanto os de chave de classificação (`StatusDate`), juntamente com operadores de comparação. 

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

  Observe a sintaxe do Python para operadores de comparação:
  + `HostId__eq=user` especifica o operador de comparação de igualdade.
  + `StatusDate__beginswith=status` especifica o operador de comparação `BEGINS_WITH`.
+ A função consulta a tabela `Games` usando o índice `OpponentId-StatusDate-index`. 

  ```
  oppGamesInProgress = self.cm.getGamesTable().query(Opponent__eq=user,
                                               StatusDate__beginswith=status,
                                               index="OpponentId-StatusDate-index",
                                               limit=10)
  ```
+ Em seguida, a função combina as duas listas, classifica, e para os primeiros itens de 0 a 10, cria uma lista dos objetos `Game` e retorna a lista para a função de chamada (ou seja, o índice).

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

### Página de jogos
<a name="TicTacToe.Phase2.AppInAction.GamePage"></a>

A página de jogos é onde o usuário joga os jogos da velha. Ela mostra a grade do jogo junto com as informações relevantes do jogo. A captura de tela a seguir mostra um jogo de exemplo em andamento:

![Captura de tela mostrando um jogo da velha em andamento.](http://docs.aws.amazon.com/pt_br/amazondynamodb/latest/developerguide/images/tic-tac-toe-example-board-10.png)


O aplicativo exibe a página de jogos nas seguintes situações:
+ O usuário cria um jogo convidando outro usuário para jogar. 

  Neste caso, a página mostra o usuário como host e o status do jogo como `PENDING`, aguardando o oponente aceitar.
+ O usuário aceita um dos convites pendentes na página inicial. 

  Neste caso, a página mostra o usuário como o oponente e o status do jogo como `IN_PROGRESS`.

Uma seleção do usuário no quadro gera uma solicitação `POST` de formato para o aplicativo. Ou seja, o Flask chama a função `selectSquare` (em `application.py`) com os dados no formato HTML. Essa função, por sua vez, chama a função `updateBoardAndTurn` (em `gameController.py`) para atualizar o item de jogo da seguinte forma:
+ Ela adiciona um novo atributo específico ao movimento.
+ Ela atualiza o valor do atributo `Turn` para o usuário cuja vez é a próxima.

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

A função retorna verdadeiro se a atualização do item foi bem-sucedida; caso contrário, retorna falso. Observe o seguinte sobre a função `updateBoardAndTurn`:
+ A função chama a função `update_item` do SDK for Python para fazer um conjunto finito de atualizações em um item existente. A função é mapeada na operação `UpdateItem` no DynamoDB. Para obter mais informações, consulte [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html). 
**nota**  
A diferença entre as operações `UpdateItem` e `PutItem` é que `PutItem` substitui o item inteiro. Para obter mais informações, consulte [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html).

Para a chamada `update_item`, o código identifica o seguinte:
+ A chave primária da tabela `Games` (ou seja, `ItemId`).

  ```
  key = { "GameId" : { "S" : gameId } }
  ```
+ O novo atributo a ser adicionado, específico para o movimento do usuário atual, e seu valor (por exemplo, `TopLeft="X"`).

  ```
  attributeUpdates = {
      position : {
          "Action" : "PUT",
          "Value" : { "S" : representation }
      }
  }
  ```
+ Condições que devem ser verdadeiras para a atualização acontecer:
  + O jogo deve estar em andamento. Ou seja, o valor do atributo `StatusDate` deve começar com `IN_PROGRESS`.
  + A vez atual deve ser de um usuário válido, conforme especificado pelo atributo `Turn`. 
  + O quadrado que o usuário escolheu deve estar disponível. Ou seja, o atributo correspondente ao quadrado não deve existir.

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

Agora, a função chama `update_item` para atualizar o item.

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

Depois que a função retorna, as chamadas da função `selectSquare` são redirecionadas conforme mostrado no exemplo a seguir.

```
redirect("/game="+gameId) 
```

Essa chamada faz com que o navegador seja atualizado. Como parte dessa atualização, o aplicativo verifica se o jogo terminou em uma vitória ou empate. Em caso afirmativo, o aplicativo atualizará o item de jogo adequadamente. 