Entità, proprietà e chiavi

Nota: gli sviluppatori che creano nuove applicazioni sono vivamente incoraggiati a utilizzare la libreria client NDB, che offre diversi vantaggi rispetto a questa libreria client, come la memorizzazione nella cache automatica delle entità tramite l'API Memcache. Se attualmente utilizzi la libreria client DB precedente, leggi la Guida alla migrazione dal database a NDB

Gli oggetti dati in Datastore sono noti come entità. Un'entità ha una o più proprietà denominate, ciascuna delle quali può avere uno o più valori. Le entità dello stesso tipo non devono necessariamente avere le stesse proprietà e i valori di un'entità per una determinata proprietà non devono essere tutti dello stesso tipo di dati. Se necessario, un'applicazione può stabilire e applicare queste restrizioni nel proprio modello dei dati.

Datastore supporta diversi tipi di dati per i valori delle proprietà. Eccone alcuni:

  • Numeri interi
  • Numeri con virgola mobile
  • Corde
  • Date
  • Dati binari

Per un elenco completo dei tipi, consulta Proprietà e tipi di valori.

Ogni entità in Datastore ha una chiave che la identifica in modo univoco. La chiave è costituita dai seguenti componenti:

  • Lo spazio dei nomi dell'entità, che consente la multitenancy
  • Il kind dell'entità, che la classifica ai fini delle query Datastore
  • Un identificatore per la singola entità, che può essere
    • Una stringa del nome della chiave
    • un ID numerico intero
  • Un percorso predecessore facoltativo che individua l'entità all'interno della gerarchia Datastore

Un'applicazione può recuperare una singola entità da Datastore utilizzando la chiave dell'entità oppure può recuperare una o più entità inviando una query basata sulle chiavi o sui valori delle proprietà delle entità.

L'SDK Python App Engine include una libreria di modellazione dei dati per rappresentare le entità Datastore come istanze di classi Python e per archiviare e recuperare queste istanze in Datastore.

Datastore non applica alcuna limitazione sulla struttura delle entità, ad esempio se una determinata proprietà ha un valore di un determinato tipo; questa attività viene lasciata all'applicazione e alla libreria di modellazione dei dati.

Tipi e identificatori

Ogni entità Datastore è di un tipo particolare,che classifica l'entità ai fini delle query: ad esempio, un'applicazione per le risorse umane potrebbe rappresentare ciascun dipendente di un'azienda con un'entità di tipo Employee. Nell'API Python Datastore, il tipo di un'entità è determinato dalla relativa classe modello, che nell'applicazione definisci come sottoclasse della classe della libreria di modellazione dei dati db.Model. Il nome della classe del modello diventa il tipo di entità che le appartengono. Tutti i nomi dei tipi che iniziano con due trattini bassi (__) sono riservati e non possono essere utilizzati.

L'esempio seguente crea un'entità di tipo Employee, compila i valori delle relative proprietà e la salva in Datastore:

import datetime
from google.appengine.ext import db

class Employee(db.Model):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hire_date = db.DateProperty()
  attended_hr_training = db.BooleanProperty()

employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

La classe Employee dichiara quattro proprietà per il modello dei dati: first_name, last_name, hire_date e attended_hr_training. La superclasse Model garantisce che gli attributi degli oggetti Employee siano conformi a questo modello: ad esempio, un tentativo di assegnare un valore di stringa all'attributo hire_date causerebbe un errore di runtime, poiché il modello dei dati per hire_date è stato dichiarato come db.DateProperty.

Oltre a un tipo, ogni entità ha un identificatore, che viene assegnato al momento della creazione dell'entità. Poiché fa parte della chiave dell'entità, l'identificatore è associato in modo permanente all'entità e non può essere modificato. Può essere assegnato in due modi:

  • L'applicazione può specificare la propria stringa nome chiave per l'entità.
  • Puoi fare in modo che Datastore assegni automaticamente all'entità un ID numerico intero.

Per assegnare un nome chiave a un'entità, fornisci l'argomento denominato key_name al costruttore della classe del modello quando crei l'entità:

# Create an entity with the key Employee:'asalieri'.
employee = Employee(key_name='asalieri')

Per fare in modo che Datastore assegni automaticamente un ID numerico, ometti l'argomento key_name:

# Create an entity with a key such as Employee:8261.
employee = Employee()

Assegnazione degli identificatori

Datastore può essere configurato per generare ID automatici utilizzando due diversi criteri per gli ID automatici:

  • Il criterio default genera una sequenza casuale di ID non utilizzati, distribuiti approssimativamente in modo uniforme. Ogni ID può contenere fino a 16 cifre decimali.
  • Il criterio legacy crea una sequenza di ID interi più piccoli non consecutivi.

Se vuoi mostrare gli ID entità all'utente e/o dipendono dal loro ordine, la soluzione migliore è utilizzare l'allocazione manuale.

Datastore genera una sequenza casuale di ID inutilizzati che sono distribuiti approssimativamente in modo uniforme. Ogni ID può contenere fino a 16 cifre decimali.

Percorsi predecessori

Le entità in Cloud Datastore formano uno spazio strutturato in modo gerarchico simile alla struttura di directory di un file system. Quando crei un'entità, puoi eventualmente indicare un'altra entità come parent;; la nuova entità è un'entità parent; dell'entità padre (tieni presente che, a differenza di un file system, l'entità padre non deve necessariamente esistere). Un'entità senza un'entità padre è un'entità principale. L'associazione tra un'entità e l'entità padre è permanente e non può essere modificata una volta creata l'entità. Cloud Datastore non assegnerà mai lo stesso ID numerico a due entità con la stessa entità padre o a due entità radice (senza un elemento padre).

L'entità padre, l'entità padre e così via in modo ricorsivo, sono i suoi antenati; i suoi figli, i figli di figli e così via sono i suoi discendenti. Un'entità base e tutti i suoi discendenti appartengono allo stesso gruppo di entità. La sequenza di entità che inizia con un'entità radice e che prosegue dalla risorsa padre a quella figlio e che porta a una determinata entità, costituisce il percorso predecessore di quell'entità. La chiave completa che identifica l'entità è composta da una sequenza di coppie tipo-identificatore che specifica il percorso predecessore e termina con quelli dell'entità stessa:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Per unentità base, il percorso predecessore è vuoto e la chiave è costituita esclusivamente dal tipo e dall'identificatore dell'entità:

[Person:GreatGrandpa]

Questo concetto è illustrato dal seguente diagramma:

Mostra la relazione tra entità base e entità secondarie nel gruppo di entità

Per designare l'entità padre di un'entità, utilizza l'argomento parent per il costruttore della classe del modello quando crei l'entità figlio. Il valore di questo argomento può essere l'entità padre stessa o la sua chiave. Per ottenere la chiave, chiama il metodo key() dell'entità padre. L'esempio seguente crea un'entità di tipo Address e mostra due modi per designare un'entità Employee come principale:

# Create Employee entity
employee = Employee()
employee.put()

# Set Employee as Address entity's parent directly...
address = Address(parent=employee)

# ...or using its key
e_key = employee.key()
address = Address(parent=e_key)

# Save Address entity to datastore
address.put()

Transazioni e gruppi di entità

Ogni tentativo di creare, aggiornare o eliminare un'entità avviene nel contesto di una transazione. Una singola transazione può includere un numero illimitato di operazioni di questo tipo. Per mantenere la coerenza dei dati, la transazione assicura che tutte le operazioni che contiene vengano applicate a Datastore come unità oppure, se una delle operazioni non va a buon fine, che nessuna di queste venga applicata. Inoltre, tutte le letture a elevata coerenza (query o get dei predecessori) eseguite all'interno della stessa transazione osservano uno snapshot coerente dei dati.

Come accennato in precedenza, un gruppo di entità è un insieme di entità connesse tramite discendenza a un elemento radice comune. L'organizzazione dei dati in gruppi di entità può limitare le transazioni che possono essere eseguite:

  • Tutti i dati a cui accede una transazione devono essere contenuti in un massimo di 25 gruppi di entità.
  • Se vuoi utilizzare query all'interno di una transazione, i dati devono essere organizzati in gruppi di entità in modo da poter specificare filtri dei predecessori che associano i dati corretti.
  • Esiste un limite di velocità effettiva di scrittura di circa una transazione al secondo all'interno di un singolo gruppo di entità. Questa limitazione esiste perché Datastore esegue la replica sincrona e masterless di ogni gruppo di entità su un'ampia area geografica per fornire un'elevata affidabilità e tolleranza di errore.

In molte applicazioni è accettabile utilizzare la coerenza finale (ovvero una query non predecessore che comprende più gruppi di entità, che a volte possono restituire dati leggermente inattivi) quando si ottiene un'ampia panoramica di dati non correlati, quindi utilizzare un'elevata coerenza (una query da predecessore o un get di una singola entità) quando si visualizza o si modifica un singolo insieme di dati altamente correlati. In queste applicazioni, di solito è un buon approccio utilizzare un gruppo di entità separato per ogni set di dati altamente correlati. Per ulteriori informazioni, consulta la sezione Come strutturare un'elevata coerenza.

Proprietà e tipi di valori

I valori dei dati associati a un'entità sono costituiti da una o più proprietà. Ogni proprietà ha un nome e uno o più valori. Una proprietà può avere valori di più di un tipo e due entità possono avere valori di tipo diverso per la stessa proprietà. Le proprietà possono essere indicizzate o non indicizzate (le query che ordinano o filtrano su una proprietà P ignorano le entità in cui P non è indicizzata). Un'entità può avere al massimo 20.000 proprietà indicizzate.

Sono supportati i seguenti tipi di valori:

Tipo di valore Tipi Python Ordinamento Note
Numero intero int
long
Numerico Numero intero a 64 bit, firmato
Numero con virgola mobile float Numerico Doppia precisione a 64 bit,
IEEE 754
Booleano bool False<True
Stringa di testo (breve) str
unicode
Unicode
(str trattati come ASCII)
Fino a 1500 byte
Stringa di testo (lunga) db.Text Nessuna esperienza Fino a 1 megabyte

Non indicizzato
Stringa byte (breve) db.ByteString Ordine byte Fino a 1500 byte
Stringa di byte (lunga) db.Blob Nessuna esperienza Fino a 1 megabyte

Non indicizzato
Data e ora datetime.date
datetime.time
datetime.datetime
Cronologica
Punto geografico db.GeoPt Per latitudine,
poi per longitudine
Indirizzo postale db.PostalAddress Unicode
Numero di telefono db.PhoneNumber Unicode
Indirizzo email db.Email Unicode
Utente Account Google users.User Indirizzo email
in ordine Unicode
Nome utente per la messaggistica immediata db.IM Unicode
Link db.Link Unicode
Categoria db.Category Unicode
Valutazione db.Rating Numerico
Chiave Datastore db.Key In base agli elementi del percorso
(kind, identifier,
kind, identifier...)
Chiave archivio BLOB blobstore.BlobKey Ordine byte
Null NoneType Nessuna esperienza

Importante: ti consigliamo vivamente di non archiviare un UserProperty, poiché include l'indirizzo email e l'ID univoco dell'utente. Se un utente modifica il proprio indirizzo email e confronti il vecchio valore User memorizzato con il nuovo valore di User, non corrisponderanno.

Per le stringhe di testo e i dati binari non codificati (stringhe di byte), Datastore supporta due tipi di valori:

  • Le stringhe brevi (fino a 1500 byte) vengono indicizzate e possono essere utilizzate nelle condizioni di filtro delle query e nell'ordinamento.
  • Le stringhe lunghe (fino a 1 megabyte) non vengono indicizzate e non possono essere utilizzate nei filtri delle query e negli ordinamenti.
Nota: il tipo di stringa con byte lunghi è denominato Blob nell'API Datastore. Questo tipo non è correlato ai blob utilizzati nell'API Blobstore.

Quando una query riguarda una proprietà con valori di tipi misti, Datastore utilizza un ordinamento deterministico basato sulle rappresentazioni interne:

  1. Valori null
  2. Numeri a virgola fissa
    • Numeri interi
    • Date e ore
    • Valutazioni
  3. Valori booleani
  4. Sequenze di byte
    • Stringa byte
    • Stringa Unicode
    • Chiavi dell'archivio BLOB
  5. Numeri con virgola mobile
  6. Punti geografici
  7. Utenti con account Google
  8. Chiavi Datastore

Poiché le stringhe di testo lunghe e le stringhe di byte lunghi non vengono indicizzate, non hanno alcun ordine definito.

Utilizzare le entità

Le applicazioni possono utilizzare l'API Datastore per creare, recuperare, aggiornare ed eliminare le entità. Se l'applicazione conosce la chiave completa di un'entità (o può ricavarla dalla chiave, dal tipo e dall'identificatore padre), può utilizzare la chiave per operare direttamente sull'entità. Un'applicazione può ottenere la chiave di un'entità anche come risultato di una query Datastore; consulta la pagina Query Datastore per ulteriori informazioni.

Creazione di un'entità

In Python, crei una nuova entità costruendo un'istanza di una classe del modello, completandone le proprietà se necessario e chiamando il relativo metodo put() per salvarla in Datastore. Puoi specificare il nome della chiave dell'entità passando un argomento key_name al costruttore:

employee = Employee(key_name='asalieri',
                    first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Se non specifichi un nome per la chiave, Datastore genererà automaticamente un ID numerico per la chiave dell'entità:

employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Recupero di un'entità

Per recuperare un'entità identificata da una determinata chiave, passa l'oggetto Key come argomento alla funzione db.get(). Puoi generare l'oggetto Key utilizzando il metodo della classe Key.from_path(). Il percorso completo è una sequenza di entità nel percorso predecessore, con ogni entità rappresentata dal relativo tipo (una stringa) seguito dal relativo identificatore (nome chiave o ID numerico):

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
address = db.get(address_k)

db.get() restituisce un'istanza della classe del modello appropriata. Assicurati di aver importato la classe del modello per l'entità da recuperare.

Aggiornamento di un'entità

Per aggiornare un'entità esistente, modifica gli attributi dell'oggetto, quindi chiama il relativo metodo put(). I dati dell'oggetto sovrascrivono l'entità esistente. L'intero oggetto viene inviato a Datastore con ogni chiamata a put().

Per eliminare una proprietà, elimina l'attributo dall'oggetto Python:

del address.postal_code

quindi salva l'oggetto.

Eliminazione di un'entità

Data la chiave di un'entità, puoi eliminarla con la funzione db.delete()

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
db.delete(address_k)

oppure chiamando il metodo delete() dell'entità:

employee_k = db.Key.from_path('Employee', 'asalieri')
employee = db.get(employee_k)

# ...

employee.delete()

Operazioni batch

Le funzioni db.put(), db.get() e db.delete() (e le rispettive controparti asincrone db.put_async(), db.get_async() e db.delete_async()) possono accettare un argomento elenco per agire su più entità in una singola chiamata Datastore:

# A batch put.
db.put([e1, e2, e3])

# A batch get.
entities = db.get([k1, k2, k3])

# A batch delete.
db.delete([k1, k2, k3])

Le operazioni collettive non cambiano i costi. Ti verrà addebitato il costo di ogni chiave in un'operazione batch, indipendentemente dall'esistenza di ogni chiave. Le dimensioni delle entità coinvolte in un'operazione non influiscono sui costi.

Eliminazione di entità in blocco

Se devi eliminare un numero elevato di entità, ti consigliamo di utilizzare Dataflow per eliminare le entità in blocco.

Utilizzare un elenco vuoto

Per l'interfaccia NDB, Datastore ha storicamente scritto un elenco vuoto come proprietà omessa per le proprietà statiche e dinamiche. Per mantenere la compatibilità con le versioni precedenti, questo comportamento continua a essere l'impostazione predefinita. Per eseguire l'override di questo comportamento a livello globale o in base a ListProperty, imposta l'argomento write_empty_list su true nella classe Property; l'elenco vuoto viene quindi scritto in Datastore e può essere letto come elenco vuoto.

Per l'interfaccia DB, storicamente le scritture di elenchi vuoti non erano consentite se la proprietà era dinamica: se hai tentato di farlo, si è verificato un errore. Ciò significa che non c'è alcun comportamento predefinito da conservare ai fini della compatibilità con le versioni precedenti delle proprietà dinamiche DB, quindi puoi semplicemente scrivere e leggere l'elenco vuoto nel modello dinamico senza alcuna modifica.

Tuttavia, per le proprietà statiche DB, l'elenco vuoto è stato scritto come una proprietà omessa e questo comportamento continua per impostazione predefinita per garantire la compatibilità con le versioni precedenti. Se vuoi attivare elenchi vuoti per le proprietà statiche DB, utilizza l'argomento write_empty_list per true nella classe Proprietà. L'elenco vuoto viene quindi scritto nel datastore.