Entitäten, Attribute und Schlüssel

Datenobjekte in Cloud Datastore werden als Entitäten bezeichnet. Eine Entität verfügt über ein oder mehrere benannte Attribute, von denen jedes einen oder mehrere Werte haben kann. Entitäten derselben Art müssen nicht dieselben Attribute haben und die Werte der Entitäten für ein bestimmtes Attribut müssen nicht alle denselben Datentyp haben. Falls erforderlich, kann eine Anwendung derartige Einschränkungen in einem eigenen Datenmodell festlegen und durchsetzen.

Datastore unterstützt viele Datentypen für Attributwerte. Diese umfassen unter anderem:

  • Ganzzahlen
  • Gleitkommazahlen
  • Strings
  • Datumsangaben
  • Binärdaten

Eine vollständige Liste der Typen finden Sie unter Attribute und Werttypen.

Jede Entität in Datastore hat einen Schlüssel, der sie eindeutig identifiziert. Der Schlüssel besteht aus den folgenden Komponenten:

  • Namespace der Entität, der die Mehrinstanzenfähigkeit ermöglicht
  • Die Art der Entität, mit der sie für Datastore-Abfragen kategorisiert wird
  • Kennung für die einzelne Entität, entweder
    • ein Schlüsselnamen-String
    • eine ganzzahlige numerische ID
  • Optionaler Ancestor-Pfad zur Entität innerhalb der Datastore-Hierarchie

Eine Anwendung kann eine einzelne Entität mithilfe des Entitätsschlüssels aus Datastore abrufen oder eine bzw. mehrere Entitäten mit einer Abfrage ermitteln, die auf den Schlüsseln oder Attributwerten der Entitäten basiert.

Das Go App Engine SDK enthält ein Paket, mit dem Datastore-Entitäten als Go-Strukturen dargestellt und in Datastore gespeichert und abgerufen werden können.

Datastore selbst erzwingt keine Einschränkungen für die Struktur der Entitäten, beispielsweise ob ein bestimmtes Attribut einen Wert eines bestimmten Typs hat. Diese Aufgabe bleibt der Anwendung überlassen.

Arten und Kennungen

Jede Datastore-Entität ist von einer bestimmten Art. Mit dieser kann die Entität für Abfragen kategorisiert werden. In einer Personalanwendung kann beispielsweise jeder Mitarbeiter eines Unternehmens mit einer Entität der Art Employee dargestellt werden. In der Go Datastore API geben Sie die Art einer Entität an, wenn Sie einen datastore.Key erstellen. Alle Artnamen, die mit zwei Unterstrichen (__) beginnen, sind reserviert und dürfen nicht verwendet werden.

Der folgende Beispielcode erstellt eine Entität der Art Employee, gibt ihre Attributwerte an und speichert die Entität in Datastore:

import (
	"context"
	"time"

	"google.golang.org/appengine/datastore"
)

type Employee struct {
	FirstName          string
	LastName           string
	HireDate           time.Time
	AttendedHRTraining bool
}

func f(ctx context.Context) {
	// ...
	employee := &Employee{
		FirstName: "Antonio",
		LastName:  "Salieri",
		HireDate:  time.Now(),
	}
	employee.AttendedHRTraining = true

	key := datastore.NewIncompleteKey(ctx, "Employee", nil)
	if _, err := datastore.Put(ctx, key, employee); err != nil {
		// Handle err
	}
	// ...
}

Mit dem Typ Employee werden vier Felder für das Datenmodell deklariert: FirstName, LastName, HireDate und AttendedHRTraining.

Neben einem Typ verfügt jede Entität über eine Kennung, die beim Erstellen der Entität zugewiesen wird. Weil sie zum Schlüssel der Entität gehört, ist die Kennung dauerhaft mit der Entität verknüpft und kann nicht geändert werden. Es gibt zwei Möglichkeiten der Zuweisung:

  • Die Anwendung kann ihren eigenen Schlüsselnamen-String für die Entität angeben.
  • Datastore kann der Entität automatisch eine ganzzahlige numerische ID zuweisen.

Wenn Sie einer Entität einen Schlüsselnamen zuweisen möchten, geben Sie datastore.NewKey ein nicht leeres stringID-Argument an:

// Create a key with a key name "asalieri".
key := datastore.NewKey(
	ctx,        // context.Context
	"Employee", // Kind
	"asalieri", // String ID; empty means no string ID
	0,          // Integer ID; if 0, generate automatically. Ignored if string ID specified.
	nil,        // Parent Key; nil means no parent
)

Wenn Datastore automatisch eine numerische ID zuweisen soll, verwenden Sie ein leeres stringID-Argument:

// Create a key such as Employee:8261.
key := datastore.NewKey(ctx, "Employee", "", 0, nil)
// This is equivalent:
key = datastore.NewIncompleteKey(ctx, "Employee", nil)

Kennungen zuweisen

Datastore kann so konfiguriert werden, dass automatische IDs mithilfe von zwei verschiedenen Richtlinien für automatische IDs generiert werden:

  • Die Richtlinie default generiert eine zufällige Abfolge von bisher nicht verwendeten IDs, die näherungsweise gleichmäßig verteilt sind. Jede ID kann maximal 16 Dezimalstellen enthalten.
  • Die Richtlinie legacy erstellt eine Abfolge nicht aufeinanderfolgender IDs aus kleineren Ganzzahlen.

Wenn Sie die Entitäts-IDs für den Nutzer anzeigen möchten und/oder deren Reihenfolge wichtig ist, ist eine manuelle Zuordnung die beste Lösung.

Datastore generiert eine zufällige Abfolge von nicht verwendeten IDs, die annähernd gleichmäßig verteilt sind. Jede ID kann maximal 16 Dezimalstellen enthalten.

Ancestor-Pfade

Entitäten in Cloud Datastore sind hierarchisch organisiert, ähnlich der Verzeichnisstruktur eines Dateisystems. Wenn Sie eine Entität erstellen, können Sie optional eine weitere Entität als übergeordnetes Element angeben. Die neue Entität ist dann ein untergeordnetes Element der übergeordneten Entität. Im Gegensatz zu einem Dateisystem muss die übergeordnete Entität nicht tatsächlich vorhanden sein. Eine Entität ohne übergeordnetes Element wird als Stammentität bezeichnet. Die Verknüpfung zwischen einer Entität und ihrer übergeordneten Entität ist dauerhaft und kann nicht geändert werden, nachdem die Entität erstellt wurde. Cloud Datastore weist zwei Entitäten mit derselben übergeordneten Entität oder zwei Stammentitäten (Entitäten ohne übergeordnete Entität) niemals dieselbe numerische ID zu.

Alle übergeordneten Elemente einer Entität werden als ihre Ancestors bezeichnet und alle untergeordneten Entitäten sind ihre Nachfolger. Eine Stammentität und alle ihre Nachfolger gehören zu derselben Entitätengruppe. Die Abfolge der Entitäten, von einer Stammentität über die untergeordneten Elemente bis zu einer bestimmten Entität, bildet den Ancestor-Pfad. Der vollständige Schlüssel, der die Entität identifiziert, besteht aus einer Abfolge von Art/Kennungs-Paaren, die den Ancestor-Pfad angeben und mit dem Paar der Entität selbst enden:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Bei einer Stammentität ist der Ancestor-Pfad leer und der Schlüssel besteht ausschließlich aus der eigenen Art und der eigenen Kennung der Entität:

[Person:GreatGrandpa]

Dieses Konzept wird anhand des folgenden Diagramms veranschaulicht:

Zeigt die Beziehung der Stammentität zu den untergeordneten Entitäten in der Entitätengruppe an

Das übergeordnete Element einer Entität wird mithilfe des Arguments parent auf datastore.NewKey festgelegt. Der Wert dieses Arguments sollte der Schlüssel der übergeordneten Entität sein. Im folgenden Beispiel wird eine Entität der Art Address erstellt und eine Employee-Entität als übergeordnetes Element ausgewiesen:

// Create Employee entity
employee := &Employee{ /* ... */ }
employeeKey, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "Employee", nil), employee)

// Use Employee as Address entity's parent
// and save Address entity to datastore
address := &Address{ /* ... */ }
addressKey := datastore.NewIncompleteKey(ctx, "Address", employeeKey)
_, err = datastore.Put(ctx, addressKey, address)

Transaktionen und Entitätengruppen

Jeder Vorgang zum Erstellen, Aktualisieren und Löschen einer Entität findet im Rahmen einer Transaktion statt. Eine einzelne Transaktion kann eine beliebige Anzahl solcher Vorgänge umfassen. Um die Konsistenz der Daten zu wahren, sorgt die Transaktion dafür, dass entweder alle enthaltenen Vorgänge auf Datastore als Einheit angewendet werden oder überhaupt nicht, sofern einer der Vorgänge fehlgeschlagen ist. Darüber hinaus greifen alle strikt konsistenten Lesevorgänge (Ancestor-Abfragen oder -Abrufe) innerhalb derselben Transaktion auf einen konsistenten Snapshot der Daten zurück.

Wie oben bereits erwähnt besteht eine Entitätengruppe aus einer Reihe von Entitäten, die über einen Ancestor-Pfad mit einem gemeinsamen Stammelement verbunden sind. Die Anordnung von Daten in Entitätengruppen kann einschränken, welche Transaktionen ausgeführt werden können:

  • Alle Daten, auf die eine Transaktion zugreift, müssen in maximal 25 Entitätengruppen enthalten sein.
  • Wenn Sie Abfragen innerhalb einer Transaktion verwenden möchten, müssen die Daten so in Entitätengruppen angeordnet werden, dass Sie Ancestor-Filter angeben können, die mit den richtigen Daten übereinstimmen.
  • Es gibt einen Schreibdurchsatzgrenzwert von ca. einer Transaktion pro Sekunde innerhalb einer einzelnen Entitätengruppe. Diese Begrenzung ist vorhanden, weil Datastore eine synchrone Replikation ohne Master jeder Entitätengruppe über einen breiten geografischen Bereich hinweg durchführt, um hohe Verlässlichkeit und Fehlertoleranz zu ermöglichen.

In vielen Anwendungen kann es akzeptabel sein, für den Abruf einer breiten Ansicht nicht zusammengehöriger Daten Eventual Consistency zu verwenden, also eine entitätengruppenübergreifende Nicht-Ancestor-Abfrage, die gelegentlich etwas veraltete Daten zurückgeben kann. Anschließend wird dann strikte Konsistenz (eine Ancestor-Abfrage oder ein get einer einzelnen Entität) verwendet, um eine einzelne Gruppe von eng zusammengehörigen Daten anzusehen oder zu bearbeiten. In derartigen Anwendungen ist die Gruppierung von eng zusammengehörigen Daten in Entitätsgruppen im Allgemeinen ein guter Ansatz. Weitere Informationen finden Sie unter Für strikte Konsistenz strukturieren.

Attribute und Werttypen

Die mit einer Entität verknüpften Datenwerte bestehen aus einem oder mehreren Attributen. Jedes Attribut hat einen Namen und einen oder mehrere Werte. Ein Attribut kann Werte mit mehr als einem Typ haben und zwei Entitäten können Werte unterschiedlichen Typs für dasselbe Attribut haben. Attribute können indexiert oder nicht indexiert sein. Abfragen, die nach einem Attribut A sortieren oder filtern, ignorieren Entitäten, bei denen A nicht indexiert ist. Eine Entität kann höchstens 20.000 indexierte Attribute haben.

Die folgenden Werttypen werden unterstützt:

Werttyp Go-Typ(en) Sortierfolge Hinweise
Ganzzahl int
int8
int16
int32
int64
Numerisch 64-Bit-Ganzzahl, signiert
Gleitkommazahl float32
float64
Numerisch 64 Bit mit doppelter Genauigkeit,
IEEE 754
Boolesch bool false<true
String (kurz) string Unicode
Bis zu 1.500 Byte. Werte, die länger als 1.500 Byte sind, führen zu einem Fehler in der Laufzeit.
String (lang) string (mit noindex) Bis zu 1 Megabyte

Nicht indexiert
Bytesegment (kurz) datastore.ByteString Bytereihenfolge Bis zu 1.500 Byte. Werte, die länger als 1.500 Byte sind, führen zu einem Fehler in der Laufzeit.
Bytesegment (lang) []byte Bis zu 1 Megabyte

Nicht indexiert
Datum und Uhrzeit time.Time Chronologisch
Geografischer Punkt appengine.GeoPoint Nach Breitengrad,
dann nach Längengrad
Datastore-Schlüssel *datastore.Key Nach Pfadelementen
(Art, Kennung,
Art, Kennung...)
Blobstore-Schlüssel appengine.BlobKey Bytereihenfolge

Sie können auch struct oder slice verwenden, um Attribute zusammenzufassen. Weitere Informationen finden Sie in der Referenz zu Datastore.

Wenn eine Abfrage ein Attribut mit Werten verschiedener Typen enthält, verwendet Datastore eine deterministische Sortierung anhand der internen Darstellungen:

  1. Nullwerte
  2. Festkommazahlen
    • Ganzzahlen
    • Datums- und Uhrzeitwerte
  3. Boolesche Werte
  4. Bytesequenzen
    • Bytesegmente (kurz)
    • Unicode-String
    • Blobstore-Schlüssel
  5. Gleitkommazahlen
  6. Geografische Punkte
  7. Datastore-Schlüssel

Da lange Bytesegmente und lange Strings nicht indexiert werden, wird für sie keine Reihenfolge definiert.

Mit Entitäten arbeiten

Anwendungen können mit der Datastore API Entitäten erstellen, abrufen, aktualisieren und löschen. Wenn die Anwendung den vollständigen Schlüssel für eine Entität kennt (oder ihn aus dem übergeordneten Schlüssel, der Art und der Kennung ableiten kann), kann sie mit dem Schlüssel direkt mit der Entität arbeiten. Eine Anwendung kann den Schlüssel einer Entität auch als Ergebnis einer Datastore-Abfrage abrufen. Weitere Informationen finden Sie unter Datastore-Abfragen.

Entität erstellen

Zum Erstellen einer neuen Entität in Go müssen Sie eine Instanz eines Go-Struct-Objekts anlegen, Werte für die zugehörigen Felder festlegen und die Entität mit datastore.Put in Datastore speichern. Nur mit einem Großbuchstaben beginnende exportierte Felder werden in Datastore gespeichert. Sie können den Schlüsselnamen der Entität durch Übergabe eines nicht leeren stringID-Arguments an datastore.NewKey angeben:

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
_, err = datastore.Put(ctx, key, employee)

Wenn Sie einen leeren Schlüsselnamen bereitstellen oder datastore.NewIncompleteKey verwenden, generiert Datastore automatisch eine numerische ID für den Schlüssel der Entität:

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewIncompleteKey(ctx, "Employee", nil)
_, err = datastore.Put(ctx, key, employee)

Entität abrufen

Wenn Sie eine Entität mit einem bestimmten Schlüssel abrufen möchten, übergeben Sie den *datastore.Key als Argument an die datastore.Get-Funktion. Sie können den *datastore.Key mit der Funktion datastore.NewKey generieren.

employeeKey := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
addressKey := datastore.NewKey(ctx, "Address", "", 1, employeeKey)
var addr Address
err = datastore.Get(ctx, addressKey, &addr)

datastore.Get füllt eine Instanz des entsprechenden Go-STRUCTs mit Werten.

Entität aktualisieren

Zum Aktualisieren einer vorhandenen Entität ändern Sie die Attribute der Struktur und rufen anschließend datastore.Put. Die Daten überschreiben die vorhandene Entität. Bei jedem Aufruf von datastore.Put wird das ganze Objekt an Datastore gesendet.

Entität löschen

Sie können eine Entität unter Angabe ihres Schlüssels mit der Funktion datastore.Delete löschen.

key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
err = datastore.Delete(ctx, key)

Batchvorgänge

datastore.Put, datastore.Get und datastore.Delete haben mehrere Varianten: datastore.PutMulti , datastore.GetMulti und datastore.DeleteMulti. Sie ermöglichen die Verarbeitung mehrerer Entitäten in einem einzigen Datastore-Aufruf:

// A batch put.
_, err = datastore.PutMulti(ctx, []*datastore.Key{k1, k2, k3}, []interface{}{e1, e2, e3})

// A batch get.
var entities = make([]*T, 3)
err = datastore.GetMulti(ctx, []*datastore.Key{k1, k2, k3}, entities)

// A batch delete.
err = datastore.DeleteMulti(ctx, []*datastore.Key{k1, k2, k3})

Die Durchführung von Aktionen als Batchvorgänge hat keine Auswirkungen auf Ihre Kosten. Jeder Schlüssel in einem Batchvorgang wird unabhängig davon in Rechnung gestellt, ob der Schlüssel vorhanden ist oder nicht. Die Größe der Entitäten in einem Vorgang wirkt sich nicht auf die Kosten aus.

.