

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Langkah 2: Periksa model data dan detail implementasi
<a name="TicTacToe.Phase2"></a>

**Topics**
+ [2.1: Model data dasar](#TicTacToe.Phase2.DataModel)
+ [2.2: Aplikasi dalam tindakan (kode panduan)](#TicTacToe.Phase2.AppInAction)

## 2.1: Model data dasar
<a name="TicTacToe.Phase2.DataModel"></a>

Aplikasi contoh ini menyoroti konsep model data DynamoDB berikut:
+ ****Tabel**** – Di DynamoDB, tabel adalah kumpulan item (yaitu, catatan), dan setiap item adalah kumpulan pasangan nama-nilai yang disebut atribut.

  Dalam Tic-Tac-Toe contoh ini, aplikasi menyimpan semua data game dalam tabel,`Games`. Aplikasi ini membuat satu item dalam tabel per game dan menyimpan semua data game sebagai atribut. Game tic-tac-toe dapat memiliki hingga sembilan langkah. Karena tabel DynamoDB tidak memiliki skema jika hanya kunci primer yang merupakan atribut yang diperlukan, aplikasi dapat menyimpan sejumlah atribut yang bervariasi per item game.

  Tabel `Games` memiliki kunci primer sederhana yang dibuat dari satu atribut, `GameId`, jenis string. Aplikasi ini menetapkan ID unik untuk setiap game. Untuk informasi selengkapnya tentang kunci primer DynamoDB, lihat [Kunci primer](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey). 

  Saat pengguna memulai game tic-tac-toe dengan mengundang pengguna lain untuk ikut bermain, aplikasi ini menciptakan item baru di tabel `Games` dengan atribut yang menyimpan metadata game, seperti berikut:
  + `HostId`, pengguna yang memulai game.
  + `Opponent`, pengguna yang diundang untuk ikut bermain.
  + Pengguna yang mendapat giliran bermain. Pengguna yang memulai game akan bermain terlebih dahulu.
  + Pengguna yang menggunakan simbol **O** di papan. Pengguna yang memulai game menggunakan simbol **O**.

  Selain itu, aplikasi membuat atribut bersambung `StatusDate`, menandai status game awal sebagai `PENDING`. Tangkapan layar berikut menampilkan contoh item seperti yang muncul di konsol DynamoDB:  
![Tangkapan layar konsol untuk tabel atribut.](https://docs.aws.amazon.com/id_id/amazondynamodb/latest/developerguide/images/tic-tac-toe-10.png)

  Seiring game berlangsung, aplikasi menambahkan satu atribut ke tabel untuk setiap langkah game. Nama atribut adalah posisi papan, misalnya `TopLeft` atau `BottomRight`. Misalnya, sebuah langkah mungkin memiliki atribut `TopLeft` dengan nilai `O`, atribut `TopRight` dengan nilai `O`, dan atribut `BottomRight` dengan nilai `X`. Nilai atribut adalah `O` atau `X`, tergantung pada pengguna mana yang melangkah. Contoh, pertimbangkan papan berikut ini.  
![Tangkapan layar yang menampilkan game tic-tac-toe yang telah selesai dan berakhir seri.](https://docs.aws.amazon.com/id_id/amazondynamodb/latest/developerguide/images/tic-tac-toe-30.png)
+ ****Atribut nilai bersambung**** – Atribut `StatusDate` mengilustrasikan atribut nilai bersambung. Dalam pendekatan ini, alih-alih membuat atribut terpisah untuk menyimpan status game (`PENDING`, `IN_PROGRESS`, dan `FINISHED`) dan tanggal (ketika langkah terakhir dilakukan), Anda menggabungkannya sebagai atribut tunggal, misalnya `IN_PROGRESS_2014-04-30 10:20:32`.

  Aplikasi kemudian menggunakan atribut `StatusDate` dalam membuat indeks sekunder dengan menentukan `StatusDate` sebagai kunci urutan untuk indeks. Manfaat penggunaan atribut nilai bersambung `StatusDate` diilustrasikan lebih lanjut dalam indeks yang akan dibahas selanjutnya.
+ ****Indeks sekunder global**** – Anda dapat menggunakan kunci primer tabel, `GameId`, untuk mengkueri tabel secara efisien guna menemukan item game. Untuk mengkueri tabel pada atribut selain atribut kunci primer, DynamoDB mendukung pembuatan indeks sekunder. Dalam contoh aplikasi ini, Anda membuat dua indeks sekunder berikut:   
![Screenshot yang menunjukkan host StatusDate dan indeks sekunder StatusDate global opp yang dibuat dalam contoh aplikasi.](https://docs.aws.amazon.com/id_id/amazondynamodb/latest/developerguide/images/tic-tac-toe-indexes-10.png)
  + **HostId-StatusDate-index**. Indeks ini memiliki `HostId` sebagai kunci partisi dan `StatusDate` sebagai kunci pengurutan. Anda dapat menggunakan indeks ini untuk melakukan kueri pada `HostId`, misalnya untuk menemukan game yang di-hosting oleh pengguna tertentu. 
  + **OpponentId-StatusDate-index**. Indeks ini memiliki `OpponentId` sebagai kunci partisi dan `StatusDate` sebagai kunci pengurutan. Anda dapat menggunakan indeks ini untuk melakukan kueri pada `Opponent`, misalnya untuk menemukan game yang lawannya adalah pengguna tertentu.

  Indeks ini disebut indeks sekunder global karena kunci partisi dalam indeks ini tidak sama dengan kunci partisi (`GameId`), yang digunakan dalam kunci primer tabel. 

  Perhatikan bahwa kedua indeks menentukan `StatusDate` sebagai kunci urutan. Ini akan memungkinkan hal berikut:
  + Anda dapat mengkueri menggunakan operator perbandingan `BEGINS_WITH`. Misalnya, Anda dapat menemukan semua game dengan atribut `IN_PROGRESS` yang di-hosting oleh pengguna tertentu. Dalam hal ini, operator `BEGINS_WITH` memeriksa nilai `StatusDate` yang dimulai dengan `IN_PROGRESS`.
  + DynamoDB menyimpan item dalam indeks dalam urutan yang diurutkan, berdasarkan nilai kunci urutan. Jadi, jika semua prefiks status sama (sebagai contoh, `IN_PROGRESS`), format ISO yang digunakan untuk bagian tanggal akan memiliki item yang diurutkan dari yang paling lama hingga yang paling baru. Pendekatan ini memungkinkan kueri tertentu untuk dilakukan secara efisien, misalnya berikut ini: 
    + Ambil hingga 10 game `IN_PROGRESS` terbaru yang di-hosting oleh pengguna yang masuk. Untuk kueri ini, Anda menentukan indeks `HostId-StatusDate-index`.
    + Ambil hingga 10 game `IN_PROGRESS` terbaru yang lawannya adalah pengguna yang masuk. Untuk kueri ini, Anda menentukan indeks `OpponentId-StatusDate-index`.

Untuk informasi selengkapnya tentang indeks sekunder, lihat [Meningkatkan akses data dengan indeks sekunder di DynamoDB](SecondaryIndexes.md).

## 2.2: Aplikasi dalam tindakan (kode panduan)
<a name="TicTacToe.Phase2.AppInAction"></a>

Aplikasi ini memiliki dua halaman utama:
+ ****Halaman beranda**** – Halaman ini menyediakan login yang mudah, tombol **BUAT** untuk membuat game tic-tac-toe baru, daftar game yang sedang dimainkan, riwayat game, dan undangan game yang tertunda. 

  Halaman beranda tidak disegarkan secara otomatis; Anda harus menyegarkan halaman untuk menyegarkan daftar.
+ ****Halaman game**** – Halaman ini menampilkan grid tic-tac-toe tempat pengguna bermain. 

  Aplikasi memperbarui halaman game secara otomatis setiap detik. JavaScript Di browser Anda memanggil server web Python setiap detik untuk menanyakan tabel Game apakah item game dalam tabel telah berubah. Jika sudah, JavaScript memicu penyegaran halaman sehingga pengguna melihat papan yang diperbarui.

Mari kita lihat secara detail cara kerja aplikasi.

### Halaman beranda
<a name="TicTacToe.Phase2.AppInAction.HomePage"></a>

Setelah pengguna masuk, aplikasi menampilkan tiga daftar informasi berikut.

![Tangkapan layar yang menampilkan halaman beranda aplikasi dengan 3 daftar: undangan tertunda, game sedang dimainkan, dan riwayat terkini.](https://docs.aws.amazon.com/id_id/amazondynamodb/latest/developerguide/images/tic-tac-toe-homepage-10.png)

+ ****Undangan**** – Daftar ini menampilkan hingga 10 undangan terbaru dari pengguna lain yang menunggu penerimaan oleh pengguna yang masuk. Dalam tangkapan layar sebelumnya, user1 memiliki undangan tertunda dari user5 dan user2.
+ ****Game sedang dimainkan**** – Daftar ini menampilkan hingga 10 game terbaru yang sedang dimainkan. Ini adalah game yang dimainkan pengguna secara aktif, yang berstatus `IN_PROGRESS`. Dalam tangkapan layar, user1 memainkan game tic-tac-toe secara aktif dengan user3 dan user4.
+ ****Riwayat terbaru**** – Daftar ini menampilkan hingga 10 game terbaru yang telah diselesaikan pengguna, yang berstatus `FINISHED`. Dalam game yang ditampilkan di tangkapan layar, user1 sebelumnya pernah bermain dengan user2. Untuk setiap game yang telah selesai dimainkan, daftar ini menunjukkan hasil game.

Dalam kode, fungsi `index` (di `application.py`) membuat tiga panggilan berikut untuk mengambil informasi status game:

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

Setiap panggilan ini mengembalikan daftar item dari DynamoDB yang dikemas oleh objek `Game`. Sangat mudah untuk mengekstrak data dari objek-objek ini dalam tampilan. Fungsi indeks meneruskan daftar objek ini ke tampilan untuk merender HTML.

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

 Tic-Tac-Toe Aplikasi mendefinisikan `Game` kelas terutama untuk menyimpan data game yang diambil dari DynamoDB. Fungsi tersebut mengembalikan daftar objek `Game` yang memungkinkan Anda mengisolasi aplikasi lainnya dari kode yang terkait dengan item Amazon DynamoDB. Dengan demikian, fungsi tersebut membantu Anda memisahkan kode aplikasi Anda dari detail lapisan penyimpanan data. 

Pola arsitektur yang dideskripsikan di sini juga disebut sebagai pola UI model-view-controller (MVC). Dalam hal ini, instans objek `Game` (yang mewakili data) adalah model, dan halaman HTML adalah tampilan. Pengontrol dibagi menjadi dua file. File `application.py` memiliki logika pengontrol untuk kerangka kerja Flask, dan logika bisnis diisolasi dalam file `gameController.py`. Artinya, aplikasi menyimpan segala sesuatu yang ada hubungannya dengan DynamoDB SDK dalam file tersendiri di folder `dynamodb`.

Mari kita meninjau tiga fungsi dan cara fungsi tersebut mengkueri tabel Game menggunakan indeks sekunder global untuk mengambil data yang relevan.

#### Menggunakan get GameInvites untuk mendapatkan daftar undangan game yang tertunda
<a name="TicTacToe.Phase2.GameInAction.ListInvitations"></a>

Fungsi `getGameInvites` mengambil daftar 10 undangan tertunda terbaru. Game ini telah dibuat oleh pengguna, tetapi lawan belum menerima undangan game. Untuk game tersebut, statusnya tetap `PENDING` sampai lawan menerima undangan. Jika lawan menolak undangan, aplikasi akan menghapus item yang sesuai dari tabel. 

Fungsi menentukan kueri sebagai berikut:
+ Fungsi menentukan indeks `OpponentId-StatusDate-index` untuk digunakan dengan nilai kunci indeks berikut dan operator perbandingan:
  + Kunci partisinya adalah `OpponentId` dan mengambil kunci indeks `{{user ID}}`.
  + Kunci urutannya adalah `StatusDate` dan mengambil operator perbandingan serta nilai kunci indeks `beginswith="PENDING_"`.

  Anda menggunakan indeks `OpponentId-StatusDate-index` untuk mengambil game yang mengundang pengguna yang masuk—yaitu, pengguna yang masuk adalah lawannya.
+ Kueri membatasi hasil hingga 10 item.

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

Dalam indeks, untuk setiap `OpponentId` (kunci partisi) DynamoDB menyimpan item yang diurutkan berdasarkan `StatusDate` (kunci urutan). Oleh karena itu, game yang dikembalikan oleh kueri akan berupa 10 game terbaru.

#### Menggunakan get GamesWithStatus untuk mendapatkan daftar game dengan status tertentu
<a name="TicTacToe.Phase2.GameInAction.ListGamesInProgressHistory"></a>

Setelah lawan menerima undangan game, status game berubah menjadi `IN_PROGRESS`. Setelah game selesai, status berubah menjadi `FINISHED`. 

Kueri untuk menemukan game yang sedang dimainkan atau telah selesai sama, kecuali nilai statusnya berbeda. Oleh karena itu, aplikasi mendefinisikan fungsi `getGamesWithStatus`, yang mengambil nilai status sebagai parameter. 

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

Bagian berikut membahas game yang sedang berlangsung, tetapi deskripsi yang sama juga berlaku untuk game yang sudah selesai.

Daftar game yang sedang berlangsung untuk pengguna tertentu meliputi hal berikut ini: 
+ In-progress game yang dihosting oleh pengguna 
+ In-progress permainan di mana pengguna adalah lawan 

Fungsi `getGamesWithStatus` menjalankan dua kueri berikut, setiap kali menggunakan indeks sekunder yang sesuai. 
+ Fungsi mengkueri tabel `Games` menggunakan indeks `HostId-StatusDate-index`. Untuk indeks, kueri menentukan nilai kunci primer—baik nilai kunci partisi (`HostId`) dan kunci urutan (`StatusDate`), bersama dengan operator perbandingan. 

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

  Perhatikan sintaks Python untuk operator perbandingan:
  + `HostId__eq=user` menentukan operator perbandingan kesetaraan.
  + `StatusDate__beginswith=status` menentukan operator perbandingan `BEGINS_WITH`.
+ Fungsi mengkueri tabel `Games` menggunakan indeks `OpponentId-StatusDate-index`. 

  ```
  oppGamesInProgress = self.cm.getGamesTable().query(Opponent__eq=user,
                                               StatusDate__beginswith=status,
                                               index="OpponentId-StatusDate-index",
                                               limit=10)
  ```
+ Fungsi tersebut kemudian menggabungkan dua daftar, mengurutkan, dan membuat daftar objek `Game` untuk 0 hingga 10 item pertama, serta mengembalikan daftar tersebut ke fungsi pemanggilan (yaitu, indeks).

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

### Halaman game
<a name="TicTacToe.Phase2.AppInAction.GamePage"></a>

Halaman game adalah tempat pengguna memainkan game tic-tac-toe. Halaman game menampilkan grid game bersama dengan informasi yang relevan terkait game. Tangkapan layar berikut menunjukkan contoh game yang sedang dimainkan:

![Tangkapan layar yang menampilkan game tic-tac-toe yang sedang dimainkan.](https://docs.aws.amazon.com/id_id/amazondynamodb/latest/developerguide/images/tic-tac-toe-example-board-10.png)


Aplikasi menampilkan halaman game dalam situasi-situasi berikut:
+ Pengguna membuat game yang mengundang pengguna lain untuk ikut bermain. 

  Dalam hal ini, halaman menunjukkan pengguna sebagai host dan status game sebagai `PENDING`, menunggu lawan menerima undangan.
+ Pengguna menerima salah satu undangan yang tertunda di halaman beranda. 

  Dalam hal ini, halaman menunjukkan pengguna sebagai lawan dan status game sebagai `IN_PROGRESS`.

Pemilihan pengguna di papan menghasilkan permintaan `POST` formulir untuk aplikasi. Artinya, Flask memanggil fungsi `selectSquare` (di `application.py`) dengan data formulir HTML. Fungsi ini, pada gilirannya, memanggil fungsi `updateBoardAndTurn` (di `gameController.py`) untuk memperbarui item game sebagai berikut:
+ Fungsi menambahkan atribut baru khusus untuk langkah.
+ Fungsi memperbarui nilai atribut `Turn` untuk pengguna yang mendapat giliran berikutnya.

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

Fungsi mengembalikan true jika pembaruan item berhasil; jika tidak, fungsi akan mengembalikan false. Perhatikan hal berikut terkait fungsi `updateBoardAndTurn`:
+ Fungsi memanggil fungsi `update_item` SDK untuk Python untuk membuat serangkaian pembaruan terbatas pada item yang sudah ada. Fungsi ini dipetakan ke operasi `UpdateItem` di DynamoDB. Untuk informasi selengkapnya, lihat [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html). 
**catatan**  
Perbedaan antara operasi `UpdateItem` dan `PutItem` adalah bahwa `PutItem` menggantikan seluruh item. Untuk informasi selengkapnya, lihat [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html).

Untuk panggilan `update_item`, kode mengidentifikasi berikut ini:
+ Kunci primer tabel `Games` (yaitu, `ItemId`).

  ```
  key = { "GameId" : { "S" : gameId } }
  ```
+ Atribut baru yang akan ditambahkan, khusus untuk langkah pengguna saat ini, dan nilainya (misalnya, `TopLeft="X"`).

  ```
  attributeUpdates = {
      position : {
          "Action" : "PUT",
          "Value" : { "S" : representation }
      }
  }
  ```
+ Kondisi yang harus true agar pembaruan dapat diterapkan:
  + Game harus sedang dimainkan. Yaitu, nilai atribut `StatusDate` harus diawali dengan `IN_PROGRESS`.
  + Giliran saat ini harus merupakan giliran pengguna yang valid sebagaimana ditentukan oleh atribut `Turn`. 
  + Kotak yang dipilih pengguna harus tersedia. Artinya, atribut yang sesuai dengan kotak tidak boleh ada.

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

Sekarang fungsi memanggil `update_item` untuk memperbarui item.

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

Setelah fungsi kembali, fungsi `selectSquare` memanggil pengalihan, seperti yang ditunjukkan dalam contoh berikut.

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

Panggilan ini menyebabkan browser disegarkan. Sebagai bagian dari penyegaran ini, aplikasi memeriksa apakah game berakhir dengan kemenangan atau seri. Jika game sudah selesai, aplikasi akan memperbarui item game. 