Stocker des données dans Datastore


Cette partie du tutoriel consacré au code de l'application Guestbook pour Python montre comment stocker des données structurées dans Datastore. Avec App Engine et Datastore, vous n'avez pas à vous soucier de la distribution, de la réplication ni de l'équilibrage de charge des données, car toutes ces opérations sont réalisées derrière une simple API. Vous disposez en outre d'un moteur de requêtes puissant et de transactions.

Ce tutoriel comporte plusieurs pages. Pour le suivre depuis le début et consulter les instructions concernant la configuration, consultez la page Créer un livre d'or.

Stocker les messages d'accueil envoyés

Les données sont écrites dans Datastore dans des objets appelés entités. Chaque entité possède une clé qui l'identifie de manière unique. Une entité peut éventuellement en désigner une autre comme parent. La première entité est alors un enfant de l'entité parente. Les entités du datastore forment donc un espace structuré de manière hiérarchique, semblable à la structure de répertoires d'un système de fichiers. Pour obtenir des informations détaillées, consultez la page Structurer des données pour renforcer la cohérence.

App Engine inclut une API de modélisation des données pour Python. Pour utiliser l'API de modélisation des données, l'exemple d'application importe le module google.appengine.ext.ndb. Chaque message d'accueil comprend le nom de l'auteur, le contenu du message, ainsi que la date et l'heure de publication de ce dernier. L'application affiche les messages dans l'ordre chronologique. Le code suivant définit le modèle de données :

class Author(ndb.Model):
    """Sub model for representing an author."""
    identity = ndb.StringProperty(indexed=False)
    email = ndb.StringProperty(indexed=False)

class Greeting(ndb.Model):
    """A main model for representing an individual Guestbook entry."""
    author = ndb.StructuredProperty(Author)
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)

Le code définit un modèle Greeting doté de trois propriétés : author dont la valeur est un objet Author avec l'adresse e-mail et l'identité de l'auteur, content dont la valeur est une chaîne, et date dont la valeur est de type datetime.datetime.

Certains constructeurs de propriétés acceptent d'autres paramètres permettant d'affiner la configuration de leur mode de fonctionnement. La transmission au constructeur ndb.StringProperty du paramètre indexed=False indique que les valeurs de cette propriété ne seront pas indexées. Cela évite les écritures inutiles, car l'application n'utilise jamais cette propriété dans une requête. La transmission au constructeur ndb.DateTimeProperty d'un paramètre auto_now_add=True configure le modèle de manière à fournir automatiquement aux nouveaux objets un horodatage datetime correspondant à l'heure de création de l'objet, si l'application ne fournit pas de valeur. Pour obtenir la liste complète des types de propriétés et de leurs options, consultez la page Propriétés NDB.

L'application fait appel au modèle de données pour créer des objets Greeting et les placer dans Datastore. Le gestionnaire Guestbook crée des messages d'accueil et les enregistre dans le datastore :

class Guestbook(webapp2.RequestHandler):

    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each
        # Greeting is in the same entity group. Queries across the
        # single entity group will be consistent. However, the write
        # rate to a single entity group should be limited to
        # ~1/second.
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = Author(
                    identity=users.get_current_user().user_id(),
                    email=users.get_current_user().email())

        greeting.content = self.request.get('content')
        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/?' + urllib.urlencode(query_params))

Ce gestionnaire Guestbook crée un objet Greeting, puis définit ses propriétés author et content avec les données publiées par l'utilisateur. Le parent de Greeting est une entité Guestbook. Il n'est pas nécessaire de créer l'entité Guestbook avant de la définir comme parent d'une autre entité. Dans cet exemple, le parent est utilisé comme espace réservé à des fins de transaction et de cohérence. Pour en savoir plus, consultez la page Transactions. Les objets qui ont un ancêtre commun appartiennent au même groupe d'entités. Le code ne définit pas la propriété date. Par conséquent, la propriété date est automatiquement définie sur la date actuelle, à l'aide de auto_now_add=True.

Enfin, greeting.put() enregistre le nouvel objet dans le datastore. Si nous avions obtenu cet objet à partir d'une requête, put() aurait mis à jour l'objet existant. Comme nous l'avons créé avec le constructeur de modèles, put() ajoute le nouvel objet au datastore.

Étant donné que les requêtes dans Datastore ne sont fortement cohérentes que dans les groupes d'entités, le code définit le même parent pour tous les messages d'accueil d'un livre afin de les attribuer au même groupe d'entités. Cela signifie que l'utilisateur voit toujours un message d'accueil immédiatement après son écriture. Cependant, la fréquence à laquelle vous pouvez écrire dans un même groupe d'entités est limitée à une écriture par seconde. Tenez compte de ce point lors de la conception d'une application réelle. Notez que l'utilisation de services tels que Memcache permet de réduire la probabilité qu'un utilisateur voie des résultats obsolètes lorsqu'il effectue des requêtes sur des groupes d'entités après une écriture.

Récupérer les messages d'accueil envoyés

Datastore dispose d'un moteur de requêtes sophistiqué pour les modèles de données. Comme Datastore n'est pas une base de données relationnelle traditionnelle, les requêtes ne sont pas spécifiées à l'aide de SQL. À la place, les données sont interrogées de l'une des deux manières suivantes : à l'aide de requêtes Datastore ou bien d'un langage de requête de type SQL appelé GQL. Pour accéder à l'ensemble des fonctionnalités de requête de Datastore, nous vous recommandons d'utiliser des requêtes sur GQL.

Le gestionnaire MainPage récupère et affiche les messages d'accueil envoyés précédemment. L'appel greetings_query.fetch(10) effectue la requête.

En savoir plus sur les index Datastore

Chaque requête réalisée dans Datastore est calculée à partir d'un ou de plusieurs index, c'est-à-dire des tables qui mappent des valeurs de propriété triées à des clés d'entité. App Engine peut ainsi diffuser des résultats rapidement, quelle que soit la taille du datastore de votre application. De nombreuses requêtes peuvent être calculées à partir des index intégrés. Cependant, dans le cas de requêtes plus complexes, Datastore requiert un index personnalisé. Sans index personnalisé, Datastore ne peut pas exécuter ces requêtes efficacement.

Par exemple, l'application Guestbook effectue un filtrage par livre d'or, et trie les résultats par date à l'aide d'une requête ascendante et d'un ordre de tri. Cela requiert la définition d'un index personnalisé dans le fichier index.yaml de l'application. Vous pouvez modifier ce fichier manuellement ou bien faire en sorte que cette opération soit automatique en exécutant les requêtes dans l'application localement. Une fois l'index défini dans index.yaml, le déploiement de l'application déploiera également les informations sur l'index personnalisé.

La définition de la requête dans index.yaml ressemble à ceci :

indexes:
- kind: Greeting
  ancestor: yes
  properties:
  - name: date
    direction: desc

Pour en savoir plus sur les index Datastore, consultez la page Index Datastore. Pour plus d'informations sur la spécification appropriée des fichiers index.yaml, consultez la page Configuration des index de datastore en Python.