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à del datastore:

  • La classe StructuredProperty, che consente alle entità di avere una struttura nidificata.
  • Memorizzazione nella cache automatica integrata, che solitamente offre letture rapide e poco costose tramite una cache contestuale 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 Migrazione a Cloud NDB.

Definizione di entità, chiavi e proprietà

Datastore archivia oggetti di dati, chiamati entità. Un'entità ha una o più proprietà, ovvero valori denominati di uno dei vari 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 chiave parent e un'altra chiave. Questo genitore può a sua volta avere un genitore e così via; in cima a questa "catena" c'è una chiave senza genitori, chiamata root.

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

Le entità le cui chiavi hanno la stessa radice in un gruppo di entità o un gruppo. Se le entità sono in gruppi diversi, le modifiche a queste entità a volte potrebbero sembrare "non nell'ordine corretto". Se le entità non sono correlate nella semantica dell'applicazione, va bene lo stesso. Tuttavia, se le modifiche di alcune entità devono essere coerenti, l'applicazione dovrebbe renderle parte dello stesso gruppo quando le crei.

Il seguente diagramma di relazione entità e esempio di codice mostrano come un Guestbook può avere più Greetings, ciascuno con proprietà content e date.

Mostra le relazioni tra entità 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 di modelli per l'archiviazione dei dati

Un modello è una classe che descrive un tipo di entità, inclusi i tipi e la configurazione delle sue proprietà. È più o meno simile a una tabella in SQL. È possibile creare un'entità chiamando il costruttore della classe del modello, quindi archiviarla chiamando il metodo put().

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

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 archiviare un nuovo saluto, l'applicazione crea un nuovo oggetto Greeting e chiama il proprio metodo put().

Per garantire che i saluti in un guestbook non appaiano "fuori dall'ordine", l'applicazione imposta una chiave padre durante la creazione di un nuovo Greeting. Di conseguenza, il nuovo saluto sarà nello stesso gruppo di entità degli altri saluti nello stesso guestbook. L'applicazione tiene conto di questo fatto durante l'esecuzione delle query, perché usa una query da predecessore.

Query e indici

Un'applicazione può eseguire query per trovare entità che corrispondono 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 tipica query NDB filtra le entità per tipo. In questo esempio, query_book genera una query che restituisce entità Greeting. Una query può anche specificare filtri su chiavi e valori delle proprietà delle entità. Come in questo esempio, una query può specificare un predecessore, trovando solo le entità che "appartengono a" un predecessore. Una query può specificare l'ordinamento. Se una determinata entità ha almeno un valore (possibilmente nullo) per ogni proprietà nei filtri e negli ordini e tutti i criteri di filtro sono soddisfatti dai valori della proprietà, l'entità viene restituita come risultato.

Ogni query utilizza un indice, una tabella che contiene i risultati della query nell'ordine desiderato. Il datastore sottostante gestisce automaticamente indici semplici (indici 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 rileva query per cui non sono ancora stati configurati gli indici.

Puoi ottimizzare manualmente gli indici 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 ha molte entità, la creazione di un nuovo indice richiede molto tempo; in questo caso, è consigliabile aggiornare le definizioni dell'indice prima di caricare il codice che utilizza il nuovo indice. Puoi usare la console di amministrazione per sapere quando è stata completata la creazione degli indici.

Questo meccanismo di indicizzazione supporta un'ampia 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, i join non sono supportati.

Comprensione delle scritture NDB: eseguire il commit, la convalida della cache e l'applicazione

NDB scrive i dati in passaggi:

  • Nella fase di commit, le modifiche vengono registrate dal servizio Datastore sottostante.
  • NDB invalida le cache delle entità o delle entità interessate. Di conseguenza, le letture future leggeranno (e memorizzeranno nella cache) il datastore sottostante anziché leggere i valori inattivi dalla cache.
  • Infine, forse pochi secondi dopo, il datastore sottostante applica la modifica. Rende la modifica visibile alle query globali e alle letture coerenti.

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

Se si verifica un errore durante la fase di commit, esistono nuovi tentativi automatici, ma se gli errori continuano, l'applicazione riceve un'eccezione. Se la fase di commit ha esito positivo, ma l'applicazione non riesce, viene eseguito il rollback fino al completamento quando si verifica una delle seguenti condizioni:

  • Le "sweep" di Datastore periodiche verificano la presenza di job di commit non completati e li applicano.
  • Alla successiva lettura, transazione o lettura in forte coerenza nel gruppo di entità interessato, le modifiche non ancora applicate verranno applicate prima della lettura, della scrittura o della transazione.

Questo comportamento influisce su come e quando i dati sono visibili all'applicazione. La modifica potrebbe non essere applicata completamente al datastore sottostante alcune centinaia di millisecondi dopo il ritorno della funzione NDB. Una query non predecessore eseguita durante l'applicazione di una modifica potrebbe visualizzare uno stato incoerente, ovvero parte della modifica, ma non tutta.

Transazioni e dati nella cache

La libreria client NDB può raggruppare più operazioni in una singola transazione. La transazione non può avere esito positivo finché tutte le operazioni nella transazione non vanno a buon fine; se una delle operazioni non riesce, viene eseguito automaticamente il rollback della transazione. Ciò è particolarmente utile per le applicazioni web distribuite, in cui più utenti potrebbero accedere o manipolare gli stessi dati contemporaneamente.

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 di Django, aggiungi google.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware alla voce MIDDLEWARE_CLASSES nel file settings.py di Django. È preferibile inserirlo prima di qualsiasi altra classe middleware, perché alcuni altri middleware potrebbero effettuare chiamate al datastore e queste non verranno gestite correttamente se il middleware viene richiamato prima di questo middleware. Puoi scoprire di più sul middleware Django.

Passaggi successivi

Scopri di più su: