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

Remarque : Les développeurs qui créent des applications sont vivement encouragés à utiliser la bibliothèque cliente NDB qui présente plusieurs avantages supplémentaires par rapport à cette bibliothèque cliente, tels que la mise en cache automatique des entités via l'API Memcache. Si vous utilisez actuellement l'ancienne bibliothèque cliente DB, consultez le guide de migration de DB vers NDB.

Les objets de données dans Datastore 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 Python inclut une bibliothèque de modélisation de données permettant de représenter les entités Datastore sous la forme d'instances de classes Python, ainsi que de stocker et de récupérer ces instances dans Cloud 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 et à la bibliothèque de modélisation de données.

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 Python, le genre d'une entité est déterminé par la classe de son modèle, que vous définissez dans votre application en tant que sous-classe de la classe de la bibliothèque de modélisation de données db.Model. Le nom de la classe du modèle correspond au genre des entités qui appartiennent à cette dernière. 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 datetime
from google.appengine.ext import db

class Employee(db.Model):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hire_date = db.DateProperty()
  attended_hr_training = db.BooleanProperty()

employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

La classe Employee déclare quatre propriétés pour le modèle de données : first_name, last_name, hire_date et attended_hr_training. La super-classe Model garantit que les attributs des objets Employee sont conformes à ce modèle. Par exemple, toute tentative d'attribution d'une valeur de chaîne à l'attribut hire_date entraîne une erreur d'exécution, car le modèle de données correspondant à hire_date a été déclaré en tant que db.DateProperty.

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é.
  • Datastore peut attribuer automatiquement à l'entité un ID numérique sous forme d'entier.

Pour attribuer un nom de clé à une entité, fournissez l'argument nommé key_name au constructeur de la classe Model lorsque vous créez l'entité :

# Create an entity with the key Employee:'asalieri'.
employee = Employee(key_name='asalieri')

Pour que Datastore attribue automatiquement un ID numérique, omettez l'argument key_name :

# Create an entity with a key such as Employee:8261.
employee = Employee()

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 du constructeur de la classe de modèle lors de la création de l'entité enfant. La valeur de cet argument peut correspondre à l'entité parente elle-même ou à sa clé. Vous pouvez obtenir la clé en appelant la méthode key() de l'entité parente. L'exemple suivant crée une entité du genre Address et montre deux façons de désigner une entité Employee comme parent :

# Create Employee entity
employee = Employee()
employee.put()

# Set Employee as Address entity's parent directly...
address = Address(parent=employee)

# ...or using its key
e_key = employee.key()
address = Address(parent=e_key)

# Save Address entity to datastore
address.put()

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) Python Ordre de tri Notes
Entier int
long
Numérique Entier de 64 bits, signé
Nombre à virgule flottante float Numérique Double précision 64 bits,
IEEE 754.
Booléen bool False<True
Chaîne de texte (courte) str
unicode
Unicode
(données str traitées comme des caractères ASCII)
Jusqu'à 1 500 octets
Chaîne de texte (longue) db.Text Aucun Jusqu'à 1 mégaoctet

Non indexé
Chaînes d'octets (courtes) db.ByteString Ordre des octets Jusqu'à 1 500 octets
Chaîne d'octets (longue) db.Blob Aucun Jusqu'à 1 mégaoctet

Non indexé
Date et heure datetime.date
datetime.time
datetime.datetime
Chronologique
Point géographique db.GeoPt En fonction de la latitude,
puis de la longitude
Adresse postale db.PostalAddress Unicode
Numéro de téléphone db.PhoneNumber Unicode
Adresse e-mail db.Email Unicode
Utilisateur de Google Accounts users.User Adresse e-mail
dans l'ordre Unicode
Descripteur de messagerie instantanée db.IM Unicode
Lien db.Link Unicode
Catégorie db.Category Unicode
Note db.Rating Numérique
Clé Datastore db.Key Par éléments de chemin d'accès
(genre, identifiant,
genre, identifiant, etc.)
Clé Blobstore blobstore.BlobKey Ordre des octets
Vide NoneType None

Important : Nous vous recommandons vivement d'éviter de stocker une propriété UserProperty, car elle contient l'adresse e-mail et l'ID unique de l'utilisateur. Si un utilisateur change d'adresse e-mail et que vous comparez l'ancienne valeur User stockée à la nouvelle valeur User, elles ne correspondront pas.

Pour les chaînes de texte et les données binaires non codées (chaînes d'octets), Datastore prend en charge deux types de valeurs :

  • Les chaînes courtes (jusqu'à 1 500 octets) sont indexées et peuvent être utilisées dans les conditions de filtre de requêtes et les ordres de tri.
  • Les chaînes longues (jusqu'à 1 Mo) ne sont pas indexées et ne peuvent pas être utilisées dans les filtres de requête et les ordres de tri.
Remarque : Le type d'une chaîne d'octets longue est appelé Blob dans l'API Datastore. Ce type n'est pas lié aux objets blob utilisés dans l'API Blobstore.

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
    • Notes
  3. Valeurs booléennes
  4. Séquences d'octets
    • Chaîne d'octets
    • Chaîne Unicode
    • Clés Blobstore
  5. Nombres à virgule flottante
  6. Points géographiques
  7. Utilisateurs disposant d'un compte Google
  8. Clés Datastore

Étant donné que les chaînes de texte et d'octets 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é

En Python, vous pouvez créer une entité en générant une instance d'une classe de modèle, en indiquant si nécessaire ses propriétés et en appelant sa méthode put() pour l'enregistrer dans Datastore. Vous avez la possibilité de spécifier le nom de la clé de l'entité en transmettant un argument key_name au constructeur.

employee = Employee(key_name='asalieri',
                    first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Si vous ne fournissez pas de nom de clé, Datastore génère automatiquement un ID numérique pour la clé de l'entité :

employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Récupérer une entité

Pour extraire une entité identifiée par une clé donnée, transmettez l'objet Key en tant qu'argument à la fonction db.get(). Vous pouvez générer l'objet Key à l'aide de la méthode de classe Key.from_path(). Le chemin complet correspond à une séquence d'entités du chemin d'ancêtre. Chaque entité est représentée par son genre (chaîne), suivi de son identifiant (nom de clé ou ID numérique) :

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
address = db.get(address_k)

La fonction db.get() renvoie une instance de la classe Model appropriée. Assurez-vous que vous avez importé la classe de modèle pour l'entité en cours de récupération.

Mettre à jour une entité

Pour mettre à jour une entité existante, modifiez les attributs de l'objet, puis appelez sa méthode put(). Les données de l'objet écrasent l'entité existante. L'objet entier est envoyé à Datastore à chaque appel de put().

Pour supprimer une propriété, supprimez l'attribut de l'objet Python :

del address.postal_code

Ensuite, enregistrez l'objet.

Supprimer une entité

Pour supprimer une entité, vous pouvez utiliser la fonction db.delete().

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
db.delete(address_k)

ou en appelant la propre méthode delete() de l'entité :

employee_k = db.Key.from_path('Employee', 'asalieri')
employee = db.get(employee_k)

# ...

employee.delete()

Opérations par lot

Les fonctions db.put(), db.get() et db.delete() (et leurs équivalents asynchrones db.put_async(), db.get_async() et db.delete_async()) peuvent accepter qu'un argument de liste s'applique à plusieurs entités d'un même appel Datastore :

# A batch put.
db.put([e1, e2, e3])

# A batch get.
entities = db.get([k1, k2, k3])

# A batch delete.
db.delete([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.

Suppression groupée d'entités

Si vous devez supprimer un grand nombre d'entités, nous vous recommandons d'utiliser Dataflow pour supprimer ces entités de manière groupée.

Utiliser une liste vide

Pour l'interface NDB, Datastore spécifiait jusqu'à présent une liste vide en tant que propriété omise pour les propriétés statiques et dynamiques. Pour assurer la rétrocompatibilité, ce comportement s'applique toujours par défaut. Pour ignorer ce comportement globalement ou en fonction de la propriété ListProperty, définissez l'argument write_empty_list sur true dans la classe Property. La liste vide est alors écrite dans Datastore et peut être lue en tant que telle.

Pour l'interface DB, il était jusque-là totalement interdit de créer des listes vides si la propriété était dynamique. Si vous tentiez d'effectuer cette opération, vous receviez un message d'erreur. Par conséquent, aucun comportement par défaut ne doit être conservé à des fins de rétrocompatibilité pour les propriétés dynamiques DB. Il vous suffit donc d'écrire et de lire la liste vide dans le modèle dynamique sans apporter de modification.

Toutefois, pour les propriétés statiques DB, la liste vide était spécifiée en tant que propriété omise. Ce comportement s'applique toujours par défaut pour assurer la rétrocompatibilité. Si vous souhaitez activer les listes vides pour les propriétés statiques DB, définissez l'argument write_empty_list sur true dans la classe Property. La liste vide est alors écrite dans Datastore.