Schritt 2: Überprüfen des Datenmodells und der Implementierungsdetails - Amazon-DynamoDB

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Schritt 2: Überprüfen des Datenmodells und der Implementierungsdetails

2.1: Grundlegendes Datenmodell

Diese Beispielanwendung hebt folgende DynamoDB-Datenmodellkonzepte hervor:

  • Tabelle – In DynamoDB ist eine Tabelle eine Sammlung von Elementen (das bedeutet, Datensätzen) und jedes Element ist eine Sammlung von Name-Wert-Paaren, die Attribute genannt werden.

    In diesem "TicTacToe"-Beispiel speichert die Anwendung alle Spieldaten in einer Tabelle, Games. Die Anwendung erstellt pro Spiel ein Element in der Tabelle und speichert alle Spieldaten als Attribute. Ein tic-tac-toe Spiel kann bis zu neun Züge haben. Da DynamoDB-Tabellen über kein Schema in Fällen verfügen, in denen nur der Primärschlüssel das erforderliche Attribut ist, kann die Anwendung eine unterschiedliche Anzahl von Attributen pro Spiel speichern.

    Die Tabelle Games verfügt über einen einfachen Primärschlüssel, der aus einem Attribut des Typs Zeichenfolge, GameId, besteht. Die Anwendung weist jedem Spiel eine eindeutige ID zu. Weitere Informationen zu DynamoDB-Primärschlüsseln finden Sie unter Primärschlüssel.

    Wenn ein Benutzer ein tic-tac-toe Spiel startet, indem er einen anderen Benutzer zum Spielen einlädt, erstellt die Anwendung ein neues Element in der Games Tabelle mit Attributen, in denen Spielmetadaten gespeichert werden, wie z. B. die folgenden:

    • HostId, der Benutzer, der das Spiel gestartet hat.

    • Opponent, der Benutzer, der zum Spielen eingeladen wurde.

    • Der Benutzer, der an der Reihe ist zu spielen. Der Benutzer, der das Spiel gestartet hat, beginnt als erster zu spielen.

    • Der Benutzer, der das Symbol O auf dem Brett verwendet. Der Benutzer, der die Spiele gestartet hat, verwendet das Symbol O.

    Zudem erstellt die Anwendung ein StatusDate-verkettetes Attribut, das den ersten Spielstatus als PENDING markiert. Der folgende Screenshot zeigt ein Beispielelement, wie es in der DynamoDB-Konsole angezeigt wird:

    Konsolen-Screenshot der Attributtabelle.

    Während das Spiel voranschreitet, fügt die Anwendung der Tabelle ein Attribut für jeden Spielzug hinzu. Der Attributname ist die Brettposition, zum Beispiel TopLeft oder BottomRight. Ein Zug verfügt beispielsweise über ein TopLeft-Attribut mit dem Wert O, ein TopRight-Attribut mit dem Wert O und ein BottomRight-Attribut mit dem Wert X. Der Attributwert ist entweder O oder X, abhängig davon, welcher Benutzer den Zug gemacht hat. Betrachten Sie beispielsweise das folgende Board:

    Screenshot, der ein abgeschlossenes tic-tac-toe Spiel zeigt, das mit einem Unentschieden endete.
  • Verkettete Wertattribute – Das StatusDate-Attribut zeigt ein verkettetes Wertattribut. Anstatt bei diesem Ansatz getrennte Attribute zu erstellen, um den Spielstatus (PENDING, IN_PROGRESS und FINISHED) und das Datum (wann der letzte Zug erfolgte) zu speichern, kombinieren Sie sie als ein einzelnes Attribut. Zum Beispiel IN_PROGRESS_2014-04-30 10:20:32.

    Die Anwendung nutzt dann das StatusDate-Attribut bei der Erstellung von sekundären Indizes, indem StatusDate als Sortierschlüssel für den Index angegeben wird. Der Vorteil eines StatusDate-verketteten Wertattributs wird in den Indizes, die im nächsten Abschnitt beschrieben werden, weiter dargestellt.

  • Globale sekundäre Indizes – Sie können den Primärschlüssel GameId der Tabelle nutzen, um die Tabelle effizient nach Spielelementen abzufragen. Um die Tabelle nach anderen Attributen als den Primärschlüsselattributen abzufragen, unterstützt DynamoDB die Erstellung von sekundären Indizes. In dieser Beispielanwendung erstellen Sie die folgenden zwei sekundären Indizes:

    Screenshot, der die in der Beispielanwendung erstellten oppStatusDate globalen Sekundärindizes hostStatusDate und die globalen Sekundärindizes zeigt.
    • HostId- StatusDate -index. Dieser Index hat HostId als Partitionsschlüssel und StatusDate als Sortierschlüssel. Sie können diesen Index für die Abfrage von HostId verwenden, um zum Beispiel Spiele zu finden, die von einem bestimmten Benutzer gehostet wurden.

    • OpponentId- StatusDate -index. Dieser Index hat OpponentId als Partitionsschlüssel und StatusDate als Sortierschlüssel. Sie können diesen Index für die Abfrage von Opponent verwenden, um zum Beispiel Spiele zu finden, in denen ein bestimmter Benutzer der Gegner ist.

    Diese Indizes werden globale sekundäre Indizes genannt, weil der Partitionsschlüssel in diesen Indizes nicht identisch mit dem Partitionsschlüssel (GameId) ist, der in dem Primärschlüssel der Tabelle verwendet wird.

    Beachten Sie, dass beide Indizes StatusDate als Sortierschlüssel angeben. Dies ermöglicht Folgendes:

    • Sie können Abfragen mithilfe des BEGINS_WITH-Vergleichsoperators durchführen. Sie können beispielsweise alle Spiele mit dem IN_PROGRESS-Attribut, das von einem bestimmten Benutzer gehostet wurde, finden. In diesem Fall überprüft der BEGINS_WITH-Operator den StatusDate-Wert, der mit IN_PROGRESS beginnt.

    • DynamoDB speichert die Elemente in sortierter Reihenfolge nach Sortierschlüssel in den Index. Wenn also alle Statuspräfixe identisch sind (zum Beispiel IN_PROGRESS), wird das ISO-Format, das für den Datumsteil verwendet wird, die Elemente vom ältesten zum neuesten sortiert haben. Dieser Ansatz ermöglicht das effiziente Durchführen bestimmter Abfragen, z. B. die Folgenden:

      • Abrufen von bis zu zehn der letzten IN_PROGRESS-Spiele, die der angemeldete Benutzer gehostet hat. Sie geben den HostId-StatusDate-index-Index für diese Abfrage an.

      • Abrufen von bis zu 10 der letzten IN_PROGRESS-Spiele, in denen der angemeldete Benutzer der Gegner ist. Sie geben den OpponentId-StatusDate-index-Index für diese Abfrage an.

Weitere Informationen zu den sekundären Indizes finden Sie unter Verbessern des Datenzugriffs mit sekundären Indizes.

2.2: Anwendung in Aktion (Anleitung des Codes)

Diese Anwendung hat zwei Hauptseiten:

  • Startseite — Diese Seite bietet dem Benutzer eine einfache Anmeldung, eine CREATE-Taste, um ein neues tic-tac-toe Spiel zu erstellen, eine Liste der laufenden Spiele, den Spielverlauf und alle aktiven ausstehenden Spieleinladungen.

    Die Homepage wird nicht automatisch aktualisiert. Sie müssen die Seite aktualisieren, um die Listen zu aktualisieren.

  • Spielseite — Diese Seite zeigt das tic-tac-toe Spielfeld, in dem die Nutzer spielen.

    Die Anwendung aktualisiert automatisch die Spielseite im Sekundentakt. Der JavaScript in Ihrem Browser ruft jede Sekunde den Python-Webserver auf, um die Spieltabelle abzufragen, ob sich die Spielelemente in der Tabelle geändert haben. Wenn dies der Fall ist, JavaScript wird eine Seitenaktualisierung ausgelöst, sodass der Benutzer das aktualisierte Board sieht.

Lassen Sie uns im Detail anschauen, wie die Anwendung funktioniert.

Homepage

Nachdem sich der Benutzer anmeldet, zeigt die Anwendung die folgenden drei Informationslisten.

Screenshot mit der Homepage der Anwendung mit 3 Listen: ausstehende Einladungen, laufende Spiele und aktueller Verlauf.
  • Einladungen – Die Liste zeigt bis zu zehn der letzten Einladungen von anderen an, die der angemeldete Benutzer noch nicht angenommen hat. Im vorherigen Screenshot hat Benutzer1 ausstehende Einladungen von Benutzer5 und Benutzer2.

  • Laufende Spiele – Diese Liste zeigt bis zu zehn der letzten Spiele an, die im Gange sind. Das sind Spiele, die der Benutzer aktiv spielt, die den Status IN_PROGRESS haben. Im Screenshot spielt Benutzer1 aktiv ein tic-tac-toe Spiel mit Benutzer3 und Benutzer4.

  • Aktueller Verlauf – Die Liste zeigt bis zu zehn der letzten Spiele an, die der Benutzer beendet hat, die den Status FINISHED haben. In dem im Screenshot gezeigten Spiel hat Benutzer1 zuvor mit Benutzer2 gespielt. Die Liste zeigt für jedes abgeschlossene Spiel die Spielergebnis an.

In dem Code führt die index-Funktion (in application.py) die folgenden drei Aufrufe durch, um Informationen über den Spielstatus abzurufen:

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

Jeder dieser Aufrufe gibt eine Liste von Elementen von DynamoDB zurück, die von den Game-Objekten umschlossen sind. Es ist einfach, Daten aus diesen Objekten in der Ansicht zu extrahieren. Die Indexfunktion übergibt diese Objektlisten an die Ansicht, um die HTML zu rendern.

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

Die „Tic-Tac-Toe“-Anwendung definiert die Game-Klasse in erster Linie, um die von DynamoDB abgerufenen Spieldaten zu speichern. Diese Funktionen geben Listen von Game-Objekten zurück, die es Ihnen ermöglichen, den Rest der Anwendung von dem Code zu isolieren, der zu Amazon-DynamoDB-Elementen gehört. Daher helfen diese Funktionen Ihnen dabei, Ihren Anwendungscode von den Details der Datenspeicherebene abzukoppeln.

Das hier beschriebene Architekturmuster wird auch als model-view-controller (MVC) UI-Muster bezeichnet. In diesem Fall sind die Game-Objekt-Instances (die Daten repräsentieren) das Modell und die HTML-Seite ist die Ansicht. Der Controller ist in zwei Dateien aufgeteilt. Die Datei application.py hat die Controllerlogik für das Flask-Framework und die Geschäftslogik wird in der Datei gameController.py isoliert. Das bedeutet, dass die Anwendung alles speichert, was mit DynamoDB-SDK zu tun hat, in ihrer eigenen separaten Datei im Ordner dynamodb.

Betrachten wir die drei Funktionen und wie sie die Spiele-Tabelle mithilfe der globalen sekundären Indizes abfragen, um relevante Daten abzurufen.

Wird verwendet getGameInvites , um die Liste der ausstehenden Spieleinladungen abzurufen

Die getGameInvites-Funktion ruft die Liste der zehn letzten ausstehenden Einladungen ab. Diese Spiele wurden von Benutzern erstellt, aber die Gegner haben die Spieleinladungen nicht angenommen. Für diese Spiele lautet der Status weiterhin PENDING, bis der Gegner die Einladung annimmt. Wenn der Gegner die Einladung ablehnt, entfernt die Anwendung das entsprechende Element aus der Tabelle.

Die Funktion gibt die Abfrage wie folgt an:

  • Sie gibt den Index OpponentId-StatusDate-index an, der mit den folgenden Indexschlüsselwerten und Vergleichsoperatoren verwendet wird:

    • Der Partitionsschlüssel ist OpponentId und übernimmt den Indexschlüssel user ID.

    • Der Sortierschlüssel ist StatusDate und übernimmt den Vergleichsoperator und den Indexschlüsselwert beginswith="PENDING_".

    Sie nutzen den OpponentId-StatusDate-index-Index, um Spiele abzurufen, für die der angemeldete Benutzer eingeladen ist – das bedeutet, in denen der angemeldete Benutzer der Gegner ist.

  • Die Abfrage begrenzt das Ergebnis auf zehn Elemente.

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

Für jede OpponentId (Partitionsschlüssel) im Index behält DynamoDB die Sortierung der Elemente nach StatusDate (Sortierschlüssel) bei. Daher werden ausschließlich die zehn letzten Spiele von der Abfrage zurückgegeben.

Verwenden von getGamesWith Status, um die Liste der Spiele mit einem bestimmten Status abzurufen

Nachdem ein Gegner eine Spieleinladung angenommen hat, ändert sich der Status in IN_PROGRESS. Nach dem Beenden des Spiels ändert sich der Status in FINISHED.

Abfragen für das Finden von Spielen, die entweder in Bearbeitung oder abgeschlossen sind, sind mit Ausnahme des unterschiedlichen Statuswerts identisch. Daher definiert die Anwendung die getGamesWithStatus-Funktion, die den Statuswert als Parameter übernimmt.

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

Der folgende Abschnitt erläutert laufende Spiele, aber die gleiche Beschreibung gilt ebenso für beendete Spiele.

Eine Liste der laufenden Spiele für einen bestimmten Benutzer umfasst die beiden Folgenden:

  • Laufende Spiele, die von dem Benutzer gehostet werden

  • Laufende Spiele, in denen der Benutzer der Gegner ist

Die getGamesWithStatus-Funktion führt die folgenden zwei Abfragen aus und verwendet jedes Mal den entsprechenden sekundären Index.

  • Die Funktion fragt die Tabelle Games mithilfe des Index HostId-StatusDate-index ab. Die Abfrage gibt für den Index Primärschlüsselwert an – die Partitionsschlüssel- (HostId) und die Sortierschlüsselwerte (StatusDate), zusammen mit Vergleichsoperatoren.

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

    Beachten Sie die Python-Syntax für Vergleichsoperatoren:

    • HostId__eq=user gibt den Gleichheitsvergleichsoperator an.

    • StatusDate__beginswith=status gibt den BEGINS_WITH-Vergleichsoperator an.

  • Die Funktion fragt die Tabelle Games mithilfe des Index OpponentId-StatusDate-index ab.

    oppGamesInProgress = self.cm.getGamesTable().query(Opponent__eq=user, StatusDate__beginswith=status, index="OpponentId-StatusDate-index", limit=10)
  • Die Funktion kombiniert dann die beiden Listen, sortiert sie und erstellt für die ersten 0 bis 10 Elemente eine Liste der Game-Objekte. Diese Liste gibt sie der aufrufenden Funktion (das bedeutet, dem Index) zurück.

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

Spielseite

Auf der Spieleseite spielt der Benutzer tic-tac-toe Spiele. Sie zeigt das Spielraster zusammen mit spielrelevanten Informationen an. Der folgende Screenshot zeigt ein laufendes Beispielspiel:

Screenshot, der ein tic-tac-toe laufendes Spiel zeigt.

Die Anwendung zeigt die Spielseite in den folgenden Situationen an:

  • Der Benutzer erstellt ein Spiel, um einen anderen Benutzer zum Spielen einzuladen.

    In diesem Fall zeigt die Seite den Benutzer als Host und den Spielstatus als PENDING an, während darauf gewartet wird, dass der Gegner akzeptiert.

  • Der Benutzer akzeptiert eine der ausstehenden Einladungen auf der Homepage.

    In diesem Fall zeigt die Seite den Benutzer als Gegner und den Spielstatus als IN_PROGRESS an.

Eine Benutzerauswahl auf dem Board generiert die Formularanforderung POST für die Anwendung. Das bedeutet, dass Flask die selectSquare-Funktion (in application.py) mit den HTML-Formulardaten aufruft. Diese Funktion ruft wiederum die updateBoardAndTurn-Funktion (in gameController.py) auf, um das Spielelement wie folgt zu aktualisieren:

  • Sie fügt ein neues Attribut hinzu, das für den Zug spezifisch ist.

  • Sie aktualisiert den Attributwert Turn für den Benutzer, der an der Reihe ist.

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

Die Funktion gibt True zurück, wenn das Element erfolgreich aktualisiert wurde; andernfalls False. Beachten Sie Folgendes zu der updateBoardAndTurn-Funktion:

  • Die Funktion ruft die update_item-Funktion des SDK for Python auf, um eine begrenzte Reihe von Aktualisierungen für ein vorhandenes Element durchzuführen. Die Funktion wird der Operation UpdateItem in DynamoDB zugeordnet. Weitere Informationen finden Sie unter UpdateItem.

    Anmerkung

    Der Unterschied zwischen der UpdateItem- und der PutItem-Operation besteht darin, dass PutItem das gesamte Element ersetzt. Weitere Informationen finden Sie unter PutItem.

Für den update_item-Aufruf identifiziert der Code Folgendes:

  • Den Primärschlüssel der Tabelle Games (d. h. ItemId).

    key = { "GameId" : { "S" : gameId } }
  • Das neue hinzuzufügende Attribut, das für den aktuellen Benutzerzug spezifisch ist und sein Wert (zum Beispiel TopLeft="X").

    attributeUpdates = { position : { "Action" : "PUT", "Value" : { "S" : representation } } }
  • Bedingungen, die erfüllt sein müssen, damit die Aktualisierung ausgeführt werden kann:

    • Das Spiel muss in Bearbeitung sein. Das bedeutet, dass der StatusDate-Attributwert mit IN_PROGRESS beginnen muss.

    • Der aktuelle Zug muss, wie von dem Turn-Attribut angegeben, ein gültiger Benutzerzug sein.

    • Das Quadrat, das der Benutzer ausgewählt hat, muss verfügbar sein. Das bedeutet, dass das Attribut, das dem Quadrat entspricht, nicht existieren muss.

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

Jetzt ruft die Funktion update_item auf, um das Element zu aktualisieren.

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

Nach der Rückgabe der Funktion werden die selectSquare-Funktionsaufrufe wie im folgenden Beispiel gezeigt umgeleitet.

redirect("/game="+gameId)

Dieser Aufruf bewirkt ein Aktualisieren des Browsers. Als Teil dieser Aktualisierung prüft die Anwendung, ob das Spiel mit einem Sieg oder einem Unentschieden beendet wurde. Wenn dies der Fall ist, aktualisiert die Anwendung das Spielelement entsprechend.