Visão geral da biblioteca de cliente do NDB do App Engine para Python 2

A biblioteca de cliente NDB do Google Datastore permite que os aplicativos Python do App Engine se conectem ao Datastore. A biblioteca de cliente NDB é construída sobre a antiga biblioteca DB do Datastore, incluindo os seguintes recursos de armazenamento de dados:

  • A classe StructuredProperty permite que entidades tenham estrutura aninhada.
  • O armazenamento em cache automático integrado normalmente fornece leituras rápidas e econômicas por meio de um cache no contexto e do Memcache.
  • Compatível com APIs assíncronas para ações simultâneas, além de APIs síncronas.

Nesta página, você encontra a introdução e a visão geral da biblioteca de cliente do NDB no App Engine. Para informações sobre como migrar para o Cloud NDB, que é compatível com o Python 3, consulte Como migrar para o Cloud NDB.

Como definir entidades, chaves e propriedades

O Datastore armazena objetos de dados, chamados de entidades. Uma entidade tem uma ou mais propriedades, valores especificados de um dos vários tipos de dados compatíveis. Por exemplo, uma propriedade pode ser uma string, um inteiro ou uma referência a outra entidade.

Cada entidade é identificada por uma chave, um identificador exclusivo no armazenamento de dados do aplicativo. A chave pode ter um pai, que é outra chave. Esse pai pode ter um pai e assim por diante. No topo desta "cadeia" de pais está uma chave sem pai, denominada raiz.

Mostra o relacionamento entre a entidade raiz e as entidades filho em um grupo de entidades

Entidades com chaves que têm a mesma raiz formam um grupo de entidades ou grupo. Se as entidades estiverem em grupos diferentes, as alterações nessas entidades poderão, às vezes, parecer "desarrumadas". Se as entidades não estiverem relacionadas na semântica do aplicativo, tudo bem. Mas se for preciso que as alterações de algumas entidades sejam consistentes, o aplicativo precisará torná-las parte do mesmo grupo ao criá-las.

O diagrama de relação de entidade e o exemplo de código a seguir mostram como um Guestbook pode ter vários Greetings, cada um com propriedades content e date.

Mostra os relacionamentos da entidade criados pelo exemplo de código incluído

Esse relacionamento é implementado no exemplo de código abaixo.

import cgi
import textwrap
import urllib

from google.appengine.ext import ndb

import webapp2

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

        greeting_blockquotes = []
        for greeting in greetings:
            greeting_blockquotes.append(
                '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

        self.response.out.write(textwrap.dedent("""\
            <html>
              <body>
                {blockquotes}
                <form action="/sign?{sign}" method="post">
                  <div>
                    <textarea name="content" rows="3" cols="60">
                    </textarea>
                  </div>
                  <div>
                    <input type="submit" value="Sign Guestbook">
                  </div>
                </form>
                <hr>
                <form>
                  Guestbook name:
                    <input value="{guestbook_name}" name="guestbook_name">
                    <input type="submit" value="switch">
                </form>
              </body>
            </html>""").format(
                blockquotes='\n'.join(greeting_blockquotes),
                sign=urllib.urlencode({'guestbook_name': guestbook_name}),
                guestbook_name=cgi.escape(guestbook_name)))

class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()
        self.redirect('/?' + urllib.urlencode(
            {'guestbook_name': guestbook_name}))

app = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', SubmitForm)
])

Como usar modelos para armazenar dados

Um modelo é uma classe que descreve um tipo de entidade, incluindo os tipos e a configuração das respectivas propriedades. É aproximadamente análogo a uma tabela no SQL. Uma entidade pode ser criada por meio de chamada ao construtor da classe do modelo e, em seguida, armazenada por meio de chamada ao método put().

Este código de amostra define a classe de modelo Greeting. Cada entidade Greeting tem duas propriedades: o conteúdo do texto da saudação e a data em que a saudação foi criada.

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)
class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()

Para criar e armazenar uma nova saudação, o aplicativo cria um novo objeto Greeting e chama o método put().

Para garantir que as saudações em um livro de visitas não apareçam como "fora de ordem", o aplicativo definirá uma chave pai ao criar um novo Greeting. Assim, a nova saudação estará no mesmo grupo de entidades que outras saudações no mesmo livro de visitas. O aplicativo usa esse fato ao consultar: ele usa uma consulta de ancestral.

Consultas e índices

Um aplicativo pode fazer consulta para encontrar entidades que correspondam a alguns filtros.

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

Uma consulta típica do NDB filtra entidades por tipo. Neste exemplo, query_book gera uma consulta que retorna entidades Greeting. Uma consulta também pode especificar filtros em chaves e valores da propriedade da entidade. Como neste exemplo, uma consulta pode especificar um ancestral, encontrando apenas entidades que "pertencem" a algum ancestral. Uma consulta pode especificar a ordem de classificação. Se determinada entidade tem pelo menos um valor (possivelmente nulo) para cada propriedade nos filtros e ordens de classificação e todos os critérios de filtro são atendidos pelos valores de propriedade, então essa entidade é retornada como resultado.

Toda consulta usa um índice, uma tabela que contém os resultados da consulta na ordem pretendida. O armazenamento de dados subjacente mantém automaticamente índices simples (índices que usam apenas uma propriedade).

Ele define os índices complexos em um arquivo de configuração, index.yaml. O servidor da Web de desenvolvimento adiciona automaticamente sugestões para esse arquivo quando encontra consultas que ainda não têm índices configurados.

É possível ajustar os índices manualmente editando o arquivo antes de fazer upload do aplicativo. Você pode atualizar os índices separadamente do upload do aplicativo executando gcloud app deploy index.yaml. Se o armazenamento de dados tiver muitas entidades, levará muito tempo para criar um novo índice para elas. Nesse caso, é aconselhável atualizar as definições de índice antes de fazer upload do código que usa o novo índice. Você pode usar o console de administração para saber quando é concluída a criação dos índices.

Esse mecanismo de índice aceita uma ampla variedade de consultas, sendo adequado para a maioria dos aplicativos. No entanto, ele não é compatível com alguns tipos de consultas comuns em outras tecnologias de banco de dados. Em especial, as junções não são aceitas.

Como entender as gravações do NDB: commit, invalidação de cache e aplicação

O NDB grava dados em etapas:

  • Na fase de commit, o serviço de armazenamento de dados subjacente registra as alterações.
  • O NDB invalida os caches das entidades afetadas. Assim, leituras futuras lerão do armazenamento de dados subjacente (e armazenarão em cache) em vez de lerem valores antigos do cache.
  • Por fim, talvez segundos depois, o armazenamento de dados subjacente aplica a alteração. Isso torna a alteração visível para consultas globais e leituras de consistência eventual.

A função do NDB que grava os dados (por exemplo, put()) é retornada após a invalidação do cache. A fase de aplicação ocorre de forma assíncrona.

Se houver uma falha durante a fase de commit, haverá novas tentativas automáticas, mas se as falhas continuarem, o aplicativo receberá uma exceção. Se a fase de commit for bem-sucedida, mas a aplicação falhar, a aplicação será adiada para a conclusão quando ocorrer uma das seguintes situações:

  • As "varreduras" periódicas do armazenamento de dados verificam se há jobs confirmados incompletos e os aplicam.
  • A próxima gravação, transação ou leitura de consistência forte no grupo de entidades afetado faz com que as alterações ainda não aplicadas sejam aplicadas antes da leitura, gravação ou transação.

Esse comportamento afeta como e quando os dados ficam visíveis para o aplicativo. A alteração pode não ser completamente aplicada ao armazenamento de dados subjacente algumas centenas de milissegundos após o retorno da função do NDB. Uma consulta de não ancestral executada durante a aplicação de uma alteração pode ver um estado inconsistente, ou seja, somente parte da alteração.

Transações e armazenamento de dados em cache

A biblioteca de cliente do NDB pode agrupar várias operações em uma única transação. A transação não tem êxito a menos que todas as operações nela também tenham. Se alguma das operações falhar, a transação será revertida automaticamente. Isso é especialmente útil para aplicativos da Web distribuídos, em que vários usuários podem acessar ou manipular os mesmos dados ao mesmo tempo.

O NDB usa o Memcache como serviço de cache para "pontos de acesso" nos dados. Se o aplicativo lê algumas entidades com frequência, o NDB pode lê-las rapidamente a partir do cache.

Como usar o Django com o NDB

Para usar o NDB com a estrutura da Web do Django, adicione google.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware à entrada MIDDLEWARE_CLASSES em seu arquivo Django settings.py. É melhor inserir na frente de quaisquer outras classes de middleware, porque algum outro middleware pode fazer chamadas de armazenamento de dados e elas não serão processadas adequadamente se esse middleware for invocado antes do middleware. Saiba mais sobre o middleware do Django.

Próximas etapas

Saiba mais sobre: