Entità, proprietà e chiavi

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 Go App Engine include un pacchetto per la rappresentazione delle entità Datastore come struct Go e per l'archiviazione e il recupero 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.

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 Go Datastore, specifichi il tipo di entità quando crei un elemento datastore.Key. 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 (
	"context"
	"time"

	"google.golang.org/appengine/datastore"
)

type Employee struct {
	FirstName          string
	LastName           string
	HireDate           time.Time
	AttendedHRTraining bool
}

func f(ctx context.Context) {
	// ...
	employee := &Employee{
		FirstName: "Antonio",
		LastName:  "Salieri",
		HireDate:  time.Now(),
	}
	employee.AttendedHRTraining = true

	key := datastore.NewIncompleteKey(ctx, "Employee", nil)
	if _, err := datastore.Put(ctx, key, employee); err != nil {
		// Handle err
	}
	// ...
}

Il tipo Employee dichiara quattro campi per il modello dei dati: FirstName, LastName, HireDate e AttendedHRTraining.

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 un argomento stringID non vuoto a datastore.NewKey:

// Create a key with a key name "asalieri".
key := datastore.NewKey(
	ctx,        // context.Context
	"Employee", // Kind
	"asalieri", // String ID; empty means no string ID
	0,          // Integer ID; if 0, generate automatically. Ignored if string ID specified.
	nil,        // Parent Key; nil means no parent
)

Per fare in modo che Datastore assegni automaticamente un ID numerico, utilizza un argomento stringID vuoto:

// Create a key such as Employee:8261.
key := datastore.NewKey(ctx, "Employee", "", 0, nil)
// This is equivalent:
key = datastore.NewIncompleteKey(ctx, "Employee", nil)

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 datastore.NewKey. Il valore di questo argomento deve essere la chiave dell'entità padre. L'esempio seguente crea un'entità di tipo Address e indica un'entità Employee come entità padre:

// Create Employee entity
employee := &Employee{ /* ... */ }
employeeKey, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "Employee", nil), employee)

// Use Employee as Address entity's parent
// and save Address entity to datastore
address := &Address{ /* ... */ }
addressKey := datastore.NewIncompleteKey(ctx, "Address", employeeKey)
_, err = datastore.Put(ctx, addressKey, address)

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 Go Ordinamento Note
Numero intero int
int8
int16
int32
int64
Numerico Numero intero a 64 bit, firmato
Numero con virgola mobile float32
float64
Numerico Doppia precisione a 64 bit,
IEEE 754
Booleano bool false<true
Stringa (breve) string Unicode
Fino a 1500 byte. I valori superiori a 1500 byte generano un errore durante il runtime.
Stringa (lunga) string (con noindex) Nessuna esperienza Fino a 1 megabyte

Non indicizzato
Sezione byte (breve) datastore.ByteString Ordine byte Fino a 1500 byte. I valori superiori a 1500 byte generano un errore durante il runtime.
Sezione byte (lunga) []byte Nessuna esperienza Fino a 1 megabyte

Non indicizzato
Data e ora time.Time Cronologica
Punto geografico appengine.GeoPoint Per latitudine,
poi per longitudine
Chiave Datastore *datastore.Key In base agli elementi del percorso
(kind, identifier,
kind, identifier...)
Chiave archivio BLOB appengine.BlobKey Ordine byte

Puoi anche utilizzare un struct o un slice per aggregare le proprietà. Consulta Riferimento Datastore per ulteriori dettagli.

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
  3. Valori booleani
  4. Sequenze di byte
    • Sezioni di byte (breve)
    • Stringa Unicode
    • Chiavi dell'archivio BLOB
  5. Numeri con virgola mobile
  6. Punti geografici
  7. Chiavi Datastore

Poiché le sezioni di byte lunghi e le stringhe lunghe non sono indicizzate, non hanno un 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 Go, puoi creare una nuova entità costruendo un'istanza di uno struct Go, compilando i relativi campi e chiamando datastore.Put per salvarlo in Datastore. Solo i campi esportati (che iniziano con una lettera maiuscola) verranno salvati in Datastore. Puoi specificare il nome della chiave dell'entità passando un argomento stringID non vuoto a datastore.NewKey:

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
_, err = datastore.Put(ctx, key, employee)

Se fornisci un nome chiave vuoto o utilizzi datastore.NewIncompleteKey, Datastore genererà automaticamente un ID numerico per la chiave dell'entità:

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewIncompleteKey(ctx, "Employee", nil)
_, err = datastore.Put(ctx, key, employee)

Recupero di un'entità

Per recuperare un'entità identificata da una determinata chiave, passa *datastore.Key come argomento alla funzione datastore.Get. Puoi generare *datastore.Key utilizzando la funzione datastore.NewKey.

employeeKey := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
addressKey := datastore.NewKey(ctx, "Address", "", 1, employeeKey)
var addr Address
err = datastore.Get(ctx, addressKey, &addr)

datastore.Get compila un'istanza dello struct Go appropriato.

Aggiornamento di un'entità

Per aggiornare un'entità esistente, modifica gli attributi dello struct, quindi chiama datastore.Put. I dati sovrascrivono l'entità esistente. L'intero oggetto viene inviato a Datastore con ogni chiamata a datastore.Put.

Eliminazione di un'entità

Data la chiave di un'entità, puoi eliminarla con la funzione datastore.Delete:

key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
err = datastore.Delete(ctx, key)

Operazioni batch

datastore.Put, datastore.Get e datastore.Delete hanno varianti collettive chiamate datastore.PutMulti, datastore.GetMulti e datastore.DeleteMulti. Consentono di agire su più entità in una singola chiamata Datastore:

// A batch put.
_, err = datastore.PutMulti(ctx, []*datastore.Key{k1, k2, k3}, []interface{}{e1, e2, e3})

// A batch get.
var entities = make([]*T, 3)
err = datastore.GetMulti(ctx, []*datastore.Key{k1, k2, k3}, entities)

// A batch delete.
err = datastore.DeleteMulti(ctx, []*datastore.Key{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.