Almacenar datos en Datastore

En esta parte de la explicación del código del libro de visitas de Python, se muestra cómo almacenar datos estructurados en Datastore. Con App Engine y Datastore, no tienes que preocuparte por la distribución, la replicación y el balanceo de cargas de los datos. Una API sencilla hace esto por ti y, además, obtienes un motor de consultas potente y transacciones.

Esta página forma parte de un instructivo de varias páginas. Si deseas comenzar desde el principio y ver las instrucciones para la configuración, consulta Cómo crear un libro de visitas.

Cómo almacenar saludos enviados

Los datos se escriben en Datastore en objetos denominados entidades. Cada entidad posee una clave única que la identifica. Una entidad puede designar de forma opcional otra entidad como principal. La primera entidad es secundaria respecto de la entidad principal. Las entidades del almacén de datos forman un espacio estructurado de forma jerárquica, similar a la estructura de directorios de un sistema de archivos. Si deseas obtener información detallada, consulta Estructura los datos para lograr una coherencia sólida.

App Engine incluye una API de modelado de datos para Python. Para usar la API de modelado de datos, la app de muestra importa el módulo google.appengine.ext.ndb. Cada saludo incluye el nombre del autor, el mensaje y la fecha y hora en que se publicó el mensaje. La aplicación muestra los mensajes en orden cronológico. El código siguiente define el modelo de datos:

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)

El código define un modelo Greeting con tres propiedades: author cuyo valor es un objeto Author con la dirección de correo electrónico y la identidad del autor, content cuyo valor es una string y date cuyo valor es datetime.datetime.

Con algunos constructores de propiedades se usan parámetros para configurar su comportamiento en mayor medida. Cuando pasas el constructor ndb.StringProperty, el parámetro indexed=False indica que los valores de esta propiedad no se indexarán. Así se ahorran operaciones de escritura innecesarias porque la aplicación nunca usa esa propiedad en una consulta. Si pasas al constructor ndb.DateTimeProperty un parámetro auto_now_add=True, el modelo se configurará automáticamente para que los objetos nuevos reciban una marca datetime de la hora a la que se creó el objeto, si la aplicación no proporciona el valor por otra vía. Para ver una lista completa de tipos de propiedades y sus opciones, consulta Propiedades de NDB.

La aplicación usa el modelo de datos para crear objetos Greeting nuevos y ponerlos en Datastore. El controlador Guestbook crea saludos nuevos y los guarda en el almacén de datos:

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

Este controlador Guestbook crea un objeto Greeting nuevo y, luego, configura sus propiedades author y content con los datos que publicó el usuario. El principal de Greeting es una entidad Guestbook. No es necesario crear la entidad Guestbook antes de configurarla para que sea la instancia principal de otra entidad. En este ejemplo, la entidad principal se usa como marcador de posición con fines de transacción y coherencia. Consulta la página Transacciones para obtener más información. Los objetos que comparten una entidad principal pertenecen al mismo grupo de entidad. El código no asigna un valor a la propiedad date, por lo que date se establece automáticamente en el presente mediante auto_now_add=True.

Por último, greeting.put() guarda el objeto nuevo en el almacén de datos. Si hubiéramos obtenido este objeto de una consulta, put() habría actualizado el objeto existente. Debido a que creamos este objeto con el constructor del modelo, put() agrega el objeto nuevo al almacén de datos.

Debido a que las consultas en Datastore son muy coherentes solo dentro de los grupos de entidad, el código configura la misma entidad principal para cada saludo a fin de asignar todos los saludos de un libro al mismo grupo de entidad. Esto significa que el usuario siempre ve un saludo inmediatamente después de escribirlo. Sin embargo, la velocidad a la que puedes escribir en el mismo grupo de entidad se limita a una operación de escritura por segundo. Cuando diseñes una aplicación real, recuerda este dato. Ten en cuenta que mediante el uso de servicios como Memcache, puedes reducir las probabilidades de que un usuario vea resultados obsoletos cuando realice consultas en los grupos de entidad después de una operación de escritura.

Cómo recuperar saludos enviados

Datastore cuenta con un motor de consulta sofisticado para los modelos de datos. Debido a que Datastore no es una base de datos relacional tradicional, las consultas no se especifican mediante SQL. En cambio, los datos se consultan de dos maneras: mediante consultas de Datastore o mediante el uso de un lenguaje de consulta similar a SQL denominado GQL. Para acceder a todas las funciones de consulta de Datastore, se recomienda el uso de consultas en GQL.

El controlador MainPage recupera y muestra los saludos enviados antes. La llamada greetings_query.fetch(10) realiza la consulta.

Más información sobre los índices de Datastore

Cada consulta en Datastore se procesa a partir de uno o más índices, tablas que asignan valores de propiedad ordenados a claves de entidad. Así es como App Engine puede entregar resultados con rapidez sin importar el tamaño del almacén de datos de tu aplicación. Numerosas consultas pueden procesarse a partir de los índices integrados, pero Datastore requiere el empleo de un índice personalizado para consultas más complejas. Si no se usa un índice personalizado, Datastore no puede ejecutar estas consultas de manera eficiente.

Por ejemplo, la aplicación de Libro de visitas filtra por libro de visitas y los pedidos, por fecha, mediante una consulta principal y un orden de clasificación. Esto requiere que se especifique un índice personalizado en el archivo index.yaml de la aplicación. Puedes editar este archivo de forma manual o bien automática ejecutando las consultas en la aplicación localmente. Después de definir el índice en index.yaml, se implementará la información del índice personalizado cuando se implemente la aplicación.

La definición de la consulta en index.yaml se ve de esta manera:

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

Puedes leer toda la información sobre los índices de Datastore en la página Índices de Datastore. Puedes obtener información acerca de la especificación adecuada para los archivos index.yaml en la Configuración de índice del almacén de datos de Python.