Entités, propriétés et clés

Dans Datastore, les objets de données sont appelés entités. Une entité est associée à une ou plusieurs propriétés nommées, chacune pouvant avoir une ou plusieurs valeurs. Les entités du même genre n'ont pas besoin d'avoir les mêmes propriétés, et il n'est pas nécessaire que les valeurs d'une entité pour une propriété donnée soient toutes du même type de données. (Si nécessaire, une application peut établir et appliquer de telles restrictions dans son propre modèle de données.)

Datastore est compatible avec divers types de données pour les valeurs de propriété, parmi lesquels :

  • Entiers
  • Nombres à virgule flottante
  • Chaînes
  • Dates
  • Données binaires

Pour obtenir la liste complète des types, consultez la section Propriétés et types de valeurs.

Chaque entité dans Datastore possède une clé qui l'identifie de manière unique. Cette clé comprend les composants suivants :

  • L'espace de noms de l'entité, qui permet de définir l'architecture mutualisée.
  • Le genre de l'entité, qui permet de la classer pour les besoins des requêtes Datastore.
  • Un identifiant associé à l'entité de manière individuelle, qui peut être :
    • Une chaîne de nom de clé
    • Un ID numérique sous forme d'entier.
  • Un chemin d'ancêtre facultatif localisant l'entité dans la hiérarchie Datastore

Une application peut extraire une entité individuelle de Datastore à l'aide de la clé de l'entité, ou extraire une ou plusieurs entités en émettant une requête basée sur leurs clés ou leurs valeurs de propriétés.

Le SDK App Engine Go inclut un package permettant de représenter les entités Datastore en tant que structures Go, ainsi que de les stocker et de les récupérer dans Datastore.

Datastore lui-même n'applique aucune restriction à la structure des entités pour déterminer, par exemple, si une propriété donnée a une valeur d'un type particulier. Cette tâche revient à l'application.

Genres et identifiants

Chaque entité Datastore est d'un genre particulier, ce qui permet de la classer dans une catégorie pour les besoins des requêtes. Par exemple, une application de ressources humaines peut représenter chaque employé d'une entreprise avec une entité du genre Employee. Dans l'API Datastore Go, vous spécifiez le genre d'une entité lorsque vous créez une clé datastore.Key. Tous les noms de genre commençant par deux traits de soulignement (__) sont réservés et ne peuvent pas être employés.

L'exemple suivant crée une entité du genre Employee, renseigne ses valeurs de propriété et l'enregistre dans 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
	}
	// ...
}

Le type Employee déclare quatre champs pour le modèle de données : FirstName, LastName, HireDate et AttendedHRTraining.

En plus d'un genre, chaque entité possède un identifiant qui lui est attribué au moment de sa création. Comme il fait partie de la clé de l'entité, l'identifiant est associé de manière permanente à celle-ci et ne peut pas être modifié. Il peut être attribué de deux manières :

  • L'application peut spécifier sa propre chaîne de nom de clé pour l'entité.
  • Vous pouvez demander à Datastore d'attribuer automatiquement à l'entité un ID numérique sous forme d'entier.

Pour attribuer un nom de clé à une entité, fournissez un argument stringID non vide à datastore.NewKey :

// 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
)

Pour que Datastore attribue un ID numérique automatiquement, employez un argument stringID vide :

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

Attribuer des identifiants

Datastore peut être configuré de sorte à générer des identifiants automatiques à l'aide de deux règles d'ID automatique différentes :

  • La règle default génère une séquence aléatoire d'ID inutilisés distribués d'une manière approximativement uniforme. Chaque ID peut comporter jusqu'à 16 chiffres décimaux.
  • La règle legacy crée une séquence d'ID entiers plus petits non consécutifs.

Si vous souhaitez que les ID d'entité soient visibles par l'utilisateur et/ou si vous souhaitez les afficher en fonction de leur ordre, l'allocation manuelle est la meilleure solution.

Datastore génère une séquence aléatoire d'ID inutilisés distribués de manière approximativement uniforme. Chaque ID peut comporter jusqu'à 16 chiffres décimaux.

Chemins d'ancêtre

Dans Cloud Datastore, les entités forment un espace structuré de manière hiérarchique, semblable à la structure de répertoires d'un système de fichiers. Lorsque vous créez une entité, vous pouvez éventuellement en désigner une autre comme son parent. La nouvelle entité est alors un enfant de l'entité parente. Contrairement à ce qui se produit dans un système de fichiers, l'entité parente n'a pas besoin d'exister réellement. Une entité sans parent est une entité racine. L'association entre une entité et son parent est permanente, et elle ne peut plus être modifiée une fois l'entité créée. Cloud Datastore n'attribue jamais le même ID numérique à deux entités ayant le même parent ou à deux entités racines (celles sans parent).

Les ancêtres d'une entité correspondent à son parent, au parent de son parent et ainsi de suite, de manière récursive. Ses descendants sont ses enfants, les enfants de ses enfants, etc. Une entité racine et tous ses descendants appartiennent au même groupe d'entités. La séquence d'entités commençant par une entité racine, puis allant du parent à l'enfant et menant à une entité donnée, constitue le chemin d'ancêtre de cette entité. La clé complète identifiant l'entité consiste en une séquence de paires genre/identifiant, qui spécifie son chemin d'ancêtre et se termine par les valeurs de l'entité elle-même :

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

Pour une entité racine, le chemin d'ancêtre est vide, et la clé est constituée uniquement du genre et de l'identifiant de l'entité :

[Person:GreatGrandpa]

Le schéma suivant illustre ce concept :

Affiche la relation entre l'entité racine et les entités enfants dans le groupe d'entités

Pour désigner le parent d'une entité, utilisez l'argument parent pour datastore.NewKey. La valeur de cet argument doit être la clé de l'entité parente. L'exemple suivant crée une entité du genre Address et désigne comme parent une entité Employee :

// 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)

Transactions et groupes d'entités

Toute tentative de création, de mise à jour ou de suppression d'une entité a lieu dans le cadre d'une transaction. Une même transaction peut inclure un nombre illimité d'opérations de ce type. Pour maintenir la cohérence des données, la transaction garantit que toutes les opérations qu'elle contient sont appliquées à Datastore de manière unitaire ou, en cas d'échec de l'une de ces opérations, qu'aucune d'entre elles n'est appliquée. En outre, toutes les lectures fortement cohérentes (requêtes ascendantes ou opérations "get") effectuées dans la même transaction observent un instantané cohérent des données.

Comme mentionné ci-dessus, un groupe d'entités est un ensemble d'entités connectées par le biais d'un ancêtre à un élément racine commun. L'organisation des données en groupes d'entités peut limiter le nombre de transactions pouvant être effectuées :

  • Toutes les données auxquelles accède une transaction doivent être contenues dans 25 groupes d'entités au maximum.
  • Si vous souhaitez employer des requêtes dans une transaction, les données doivent être organisées en groupes d'entités de sorte que vous puissiez spécifier des filtres d'ancêtres qui correspondent aux données appropriées.
  • Le débit en écriture est limité à environ une transaction par seconde pour un seul groupe d'entités. Cette limite a été définie car Datastore effectue une réplication synchrone sans maître de chaque groupe d'entités sur une vaste zone géographique, afin de fournir une fiabilité et une tolérance aux pannes élevées.

Dans de nombreuses applications, il est acceptable de recourir à la cohérence à terme (à savoir à une requête non ascendante couvrant plusieurs groupes d'entités et pouvant parfois renvoyer des données légèrement obsolètes) lors de l'obtention d'une vue d'ensemble de données non liées, puis de passer à la cohérence forte (requête ascendante ou opération get d'une seule entité) lors de la visualisation ou de la modification d'un seul ensemble de données étroitement liées. Dans ces applications, il est généralement recommandé d'utiliser un groupe d'entités distinct pour chaque ensemble de données étroitement liées. Pour en savoir plus, consultez la page Structurer des données pour renforcer la cohérence.

Propriétés et types de valeurs

Les valeurs de données associées à une entité consistent en une ou plusieurs propriétés. Chaque propriété a un nom, et une ou plusieurs valeurs. Une propriété peut avoir des valeurs de plus d'un type, et deux entités peuvent avoir des valeurs de types différents pour la même propriété. Les propriétés peuvent être indexées ou non indexées (les requêtes qui ordonnent ou filtrent une propriété P ignorent les entités pour lesquelles P n'est pas indexée.) Une entité peut avoir 20 000 propriétés indexées au maximum.

Les types de valeurs suivants sont acceptés :

Type de valeur Type(s) Go Ordre de tri Notes
Entier int
int8
int16
int32
int64
Numérique Entier de 64 bits, signé
Nombre à virgule flottante float32
float64
Numérique Double précision 64 bits,
IEEE 754.
Booléen bool false<true
Chaîne (courte) string Unicode
Jusqu'à 1 500 octets. Les valeurs supérieures à 1 500 octets génèrent une erreur au moment de l'exécution.
Chaîne (longue) string (avec noindex) Aucun Jusqu'à 1 mégaoctet

Non indexé
Tranche d'octets (courte) datastore.ByteString Ordre des octets Jusqu'à 1 500 octets. Les valeurs supérieures à 1 500 octets génèrent une erreur au moment de l'exécution.
Tranche d'octets (longue) []byte Aucun Jusqu'à 1 mégaoctet

Non indexé
Date et heure time.Time Chronologique
Point géographique appengine.GeoPoint En fonction de la latitude,
puis de la longitude
Clé Datastore *datastore.Key Par éléments de chemin d'accès
(genre, identifiant,
genre, identifiant, etc.)
Clé Blobstore appengine.BlobKey Ordre des octets

Vous pouvez également agréger des propriétés à l'aide d'un élément struct ou slice. Pour en savoir plus, consultez la documentation de référence de Datastore.

Lorsqu'une requête implique une propriété avec des valeurs de types mixtes, Datastore utilise un ordre déterministe basé sur les représentations internes :

  1. Valeurs Null
  2. Nombres à virgule fixe
    • Entiers
    • Dates et heures
  3. Valeurs booléennes
  4. Séquences d'octets
    • Tranches d'octets (courtes)
    • Chaîne Unicode
    • Clés Blobstore
  5. Nombres à virgule flottante
  6. Points géographiques
  7. Clés Datastore

Étant donné que les tranches d'octets longues et les chaînes longues ne sont pas indexées, aucun tri n'est défini pour celles-ci.

Utiliser des entités

Les applications peuvent utiliser l'API Datastore pour créer, récupérer, mettre à jour et supprimer des entités. Si l'application connaît la clé complète d'une entité (ou si elle peut la déduire de sa clé parente, de son genre et de son identifiant), elle peut s'en servir pour effectuer des opérations directement sur l'entité. Une application peut également obtenir la clé d'une entité à la suite d'une requête Datastore. Pour plus d'informations, consultez la page Requêtes Datastore.

Créer une entité

Dans Go, pour créer une entité, construisez une instance d'une structure Go, renseignez ses champs, puis appelez datastore.Put pour l'enregistrer dans Datastore. Seuls les champs exportés (commençant par une lettre majuscule) seront enregistrés dans Datastore. Vous pouvez spécifier le nom de clé de l'entité en transmettant un argument stringID non vide à datastore.NewKey.

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)

Si vous fournissez un nom de clé vide ou utilisez datastore.NewIncompleteKey, Datastore génère automatiquement un ID numérique pour la clé de l'entité :

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

Récupérer une entité

Pour récupérer une entité identifiée par une clé donnée, transmettez l'élément *datastore.Key en tant qu'argument à la fonction datastore.Get. Vous pouvez générer l'élément *datastore.Key à l'aide de la fonction datastore.NewKey.

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 insère une instance de la structure Go appropriée.

Mettre à jour une entité

Pour mettre à jour une entité existante, modifiez les attributs de la structure, puis appelez datastore.Put. Les données écrasent l'entité existante. L'objet entier est envoyé à Datastore à chaque appel de datastore.Put.

Supprimer une entité

Selon la clé d'une entité, vous pouvez supprimer l'entité à l'aide de la fonction datastore.Delete :

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

Opérations par lot

datastore.Put, datastore.Get et datastore.Delete ont des variantes groupées appelées datastore.PutMulti, datastore.GetMulti et datastore.DeleteMulti. Elles permettent d'intervenir sur plusieurs entités via un seul appel Datastore :

// 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})

Les opérations par lot n'entraînent pas de modification des coûts. Vous serez facturé pour chaque clé employée dans ces opérations, que cette clé existe ou non. La taille des entités impliquées dans une opération n'a pas d'incidence sur les coûts.