Panoramica della libreria client NDB di App Engine per Python 2

La libreria client NDB di Google Datastore consente alle app Python di App Engine di connettersi a Datastore. La libreria client NDB si basa sulla libreria DB Datastore precedente aggiungendo le seguenti funzionalità di datastore:

  • La classe StructuredProperty, che consente alle entità di avere una struttura nidificata.
  • Memorizzazione nella cache automatica integrata, che in genere offre letture rapide ed economiche tramite una cache in-context e Memcache.
  • Supporta sia le API asincrone per le azioni simultanee sia le API sincrone.

Questa pagina fornisce un'introduzione e una panoramica della libreria client NDB di App Engine. Per informazioni su come eseguire la migrazione a Cloud NDB, che supporta Python 3, consulta Eseguire la migrazione a Cloud NDB.

Definizione di entità, chiavi e proprietà

Datastore archivia oggetti dati, chiamati entità. Un'entità ha una o più proprietà, ovvero valori denominati di uno dei diversi tipi di dati supportati. Ad esempio, una proprietà può essere una stringa, un numero intero o un riferimento a un'altra entità.

Ogni entità è identificata da una chiave, un identificatore univoco all'interno del datastore dell'applicazione. La chiave può avere una principale, un'altra chiave. Questo elemento principale può avere a sua volta un elemento principale e così via. Al vertice di questa "catena" di elementi principali c'è una chiave senza elemento principale, chiamata radice.

Mostra la relazione tra
  l'entità base e le entità secondarie in un gruppo di entità

Le entità le cui chiavi hanno la stessa radice formano un gruppo di entità o un gruppo. Se le entità si trovano in gruppi diversi, a volte le modifiche a queste entità potrebbero sembrare "fuori sequenza". Se le entità non sono correlate nella semantica della tua applicazione, non c'è problema. Tuttavia, se le modifiche di alcune entità devono essere coerenti, l'applicazione deve inserirle nello stesso gruppo al momento della creazione.

Il seguente diagramma delle entità e l'esempio di codice mostrano come un Guestbook puoi avere più Greetings, ciascuno con proprietà content e date.

Mostra le relazioni tra entità come create dall'esempio di codice incluso

Questa relazione è implementata nell'esempio di codice riportato di seguito.

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

Utilizzo dei modelli per l'archiviazione dei dati

Un modello è una classe che descrive un tipo di entità, inclusi i tipi e la configurazione delle relative proprietà. È approssimativamente analogo a una tabella in SQL. Un'entità può essere creata chiamando il costruttore di classe del modello e poi memorizzata chiamando il metodo put().

Questo codice campione definisce la classe del modello Greeting. Ogni entità Greeting ha due proprietà: il contenuto del testo del saluto e la data di creazione del saluto.

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

Per creare e memorizzare un nuovo saluto, l'applicazione crea un nuovo oggetto Greeting e chiama il relativo metodo put().

Per assicurarsi che i saluti in un libro degli ospiti non vengano visualizzati "fuori ordine ", l'applicazione imposta una chiave principale quando viene creato un nuovo Greeting. Pertanto, il nuovo messaggio di benvenuto sarà nello stesso gruppo di entità degli altri messaggi di benvenuto nello stesso libro degli ospiti. L'applicazione utilizza questo fatto quando esegue query: utilizza una query da predecessore'antenato.

Query e indici

Un'applicazione può eseguire query per trovare entità corrispondenti ad alcuni filtri.

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

Una query NDB tipica filtra le entità in base al tipo. In questo esempio, query_book genera una query che restituisce Greeting entità. Una query può anche specificare filtri per i valori e le chiavi delle proprietà delle entità. Come in questo esempio, una query può specificare un antenato, trovando solo le entità che "appartengono" a un antenato. Una query può specificare l'ordinamento. Se una determinata entità ha almeno un valore (eventualmente nullo) per ogni proprietà nei filtri e negli ordini di ordinamento e tutti i criteri di filtro sono soddisfatti dai valori delle proprietà, l'entità viene restituita come risultato.

Ogni query utilizza un indice, una tabella che contiene i risultati della query nell'ordine desiderato. Datastore sottostante gestisce automaticamente gli indici semplici (che utilizzano una sola proprietà).

Definisce i suoi indici complessi in un file di configurazione, index.yaml. Il server web di sviluppo aggiunge automaticamente suggerimenti a questo file quando incontra query per le quali non sono ancora stati configurati gli indici.

Puoi ottimizzare gli indici manualmente modificando il file prima di caricare l'applicazione. Puoi aggiornare gli indici separatamente dal caricamento dell'applicazione eseguendo gcloud app deploy index.yaml. Se il tuo datastore contiene molte entità, la creazione di un nuovo indice richiede molto tempo. In questo caso, è consigliabile aggiornare le definizioni degli indici prima di caricare il codice che utilizza il nuovo indice. Puoi utilizzare la Console di amministrazione per scoprire quando è stata completata la creazione degli indici.

Questo meccanismo di indicizzazione supporta una vasta gamma di query ed è adatto alla maggior parte delle applicazioni. Tuttavia, non supporta alcuni tipi di query comuni in altre tecnologie di database. In particolare, le unioni non sono supportate.

Informazioni sulle scritture NDB: commit, annullamento della convalida della cache e applicazione

NDB scrive i dati in più passaggi:

  • Nella fase di commit, il servizio Datastore sottostante registra le modifiche.
  • NDB annulla la convalida delle cache dell'entità o delle entità interessate. Pertanto, le letture future leggeranno (e memorizzeranno nella cache) il Datastore sottostante anziché leggere valori non aggiornati dalla cache.
  • Infine, forse alcuni secondi dopo, Datastore di base applica la modifica. La modifica diventa visibile alle query globali e alle letture eventualmente coerenti.

La funzione NDB che scrive i dati (ad esempio put()) restituisce dopo l'invalidazione della cache; la fase di applicazione avviene in modo asincrono.

Se si verifica un errore durante la fase di commit, vengono eseguiti tentativi automatici, ma se gli errori persistono, l'applicazione riceve un'eccezione. Se la fase Commit riesce, ma l'applicazione non va a buon fine, l'applicazione viene eseguita fino al completamento quando si verifica una delle seguenti condizioni:

  • Le "esplorazioni" periodiche del Datastore controllano la presenza di job di commit non completati e li applicano.
  • La successiva scrittura, transazione o lettura fortemente coerente nel gruppo di entità interessato fa sì che le modifiche non ancora applicate vengano applicate prima della lettura, della scrittura o della transazione.

Questo comportamento influisce su come e quando i dati sono visibili alla tua applicazione. La variazione potrebbe non essere applicata completamente al Datastore sottostante dopo circa qualche hundred millisecondo dal ritorno della funzione NDB. Una query non principale eseguita durante l'applicazione di una modifica potrebbe mostrare uno stato incoerente, ovvero parte, ma non tutta, la modifica.

Transazioni e dati memorizzati nella cache

La libreria client NDB può raggruppare più operazioni in un'unica transazione. La transazione non può avere esito positivo a meno che ogni operazione al suo interno non vada a buon fine. Se una delle operazioni non va a buon fine, la transazione viene annullata automaticamente. Questo è particolarmente utile per le applicazioni web distribuite, in cui più utenti potrebbero accedere o manipolare contemporaneamente gli stessi dati.

NDB utilizza Memcache come servizio di cache per gli "hot spot" nei dati. Se l'applicazione legge spesso alcune entità, NDB può leggerle rapidamente dalla cache.

Utilizzo di Django con NDB

Per utilizzare NDB con il framework web Django, aggiungi google.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware alla entry MIDDLEWARE_CLASSES nel file settings.py di Django. È preferibile inserirlo prima di qualsiasi altra classe di middleware, perché alcuni altri middleware potrebbero effettuare chiamate al datastore e queste non verranno gestite correttamente se il middleware viene invocato prima di questo. Puoi scoprire di più sul middleware Django.

Passaggi successivi

Scopri di più su: