Entità, proprietà e chiavi

Gli oggetti di 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 avere le stesse proprietà e i valori di un'entità per una determinata proprietà non devono necessariamente essere dello stesso tipo di dati. Se necessario, un'applicazione può stabilire e applicare tali restrizioni nel suo modello dei dati).

Datastore supporta diversi tipi di dati per i valori delle proprietà. Sono inclusi, tra gli altri:

  • Numeri interi
  • Numeri in virgola mobile
  • Stringhe
  • 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 per le query Datastore
  • Un identificatore per la singola entità, che può essere:
    • una stringa del nome della chiave
    • un ID numerico intero
  • Un percorso dell'antenato facoltativo che individua l'entità all'interno della gerarchia di Datastore

Un'applicazione può recuperare una singola entità da Datastore utilizzando la relativa chiave oppure può recuperare una o più entità emettendo una query in base alle chiavi o ai valori delle proprietà delle entità.

L'SDK di Go App Engine include un pacchetto per la rappresentazione di entità Datastore come struct Go, nonché per l'archiviazione e il recupero in Datastore.

Datastore stesso non applica alcuna restrizione sulla struttura delle entità, ad esempio sul fatto che una determinata proprietà abbia un valore di un determinato tipo. questa attività viene lasciata all'applicazione.

Tipi e identificatori

Ogni entità Datastore è di un tipo specifico,che categorizza l'entità ai fini delle query: ad esempio, un'applicazione per le risorse umane potrebbe rappresentare ogni dipendente di un'azienda con un'entità di tipo Employee. Nell'API Datastore per Go, specifichi il tipo di un'entità quando crei un datastore.Key. Tutti i nomi di tipo 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 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 di dati: FirstName, LastName, HireDate e AttendedHRTraining.

Oltre a un tipo, ogni entità ha un identificatore, assegnato al momento della creazione. 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 a un'entità un nome della chiave, 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 di ID automatico:

  • Il criterio default genera una sequenza casuale di ID inutilizzati 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 dipendere dall'ordine, la cosa migliore da fare è utilizzare l'allocazione manuale.

Datastore genera una sequenza casuale di ID inutilizzati distribuiti in modo approssimativamente uniforme. Ogni ID può essere composto da un massimo di 16 cifre decimali.

Percorsi degli antenati

Le entità in Cloud Datastore formano uno spazio strutturato gerarchicamente simile la struttura di directory di un file system. Quando crei un'entità, puoi optionally designare un'altra entità come principale; la nuova entità è secondaria dell'entità principale (tieni presente che, a differenza di un file system, l'entità principale non deve necessariamente esistere). Un'entità senza un elemento padre è una root . L'associazione tra un'entità e la relativa entità è permanente e non può essere modificato dopo la creazione dell'entità. Cloud Datastore non assegnerà mai lo stesso ID numerico a due entità con lo stesso elemento padre, oppure a due radici (quelle senza un elemento padre).

L'entità principale, l'entità principale dell'entità principale e così via in modo ricorsivo sono i suoi antenati; le sue entità figlio, le entità figlio delle entità figlio e così via sono i suoi discendenti. Entità base e tutti i suoi discendenti appartengono a all'interno dello stesso gruppo di entità. La sequenza di entità che inizia con una radice dell'entità e procedere da principale a figlio, portando a una determinata entità, costituiscono il percorso predecessore dell'entità. La chiave completa che identifica l'entità è composta da una sequenza di coppie tipo-identificatore che specifica predecessore e termina con quelli dell'entità stessa:

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

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

[Person:GreatGrandpa]

Questo concetto è illustrato dal seguente diagramma:

Mostra la relazione tra entità base e quella secondaria
  entità nel gruppo di entità

Per designare l'entità principale, 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 designa un'entità Employee come principale:

// 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 qualsiasi di queste operazioni. Per mantenere dei dati, la transazione garantisce che tutte le operazioni vengono applicati a Datastore come unità oppure, se uno dei delle operazioni non riesce, nessuna di queste viene applicata. Inoltre, tutte le letture fortemente coerenti (query o get di antenati) eseguite all'interno della stessa transazione osservano un'istantanea coerente dei dati.

Come accennato sopra, un gruppo di entità è un insieme di entità collegate tramite l'ascendenza a un elemento principale comune. L'organizzazione dei dati in gruppi di entità può limitare le transazioni che è possibile eseguire:

  • Tutti i dati a cui accede una transazione devono essere contenuti in massimo 25 gruppi di entità.
  • Se vuoi utilizzare le query all'interno di una transazione, i dati devono essere organizzati in gruppi di entità in modo da poter specificare filtri di antenato che corrispondano ai 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 senza master di ogni gruppo di entità su un'ampia area geografica per garantire elevata affidabilità e tolleranza di errori.

In molte applicazioni, è accettabile utilizzare la coerenza finale (ovvero una query non predecessore che comprende più gruppi di entità, che a volte può restituire dati leggermente obsoleti) quando si ottiene un'ampia visione di dati non correlati e quindi utilizzare elevata coerenza (una query da predecessore o get di una singola entità) durante la visualizzazione o la modifica di un singolo set di dati altamente correlati. In queste applicazioni, in genere è consigliabile utilizzare un gruppo di entità distinto per ogni insieme di dati altamente correlati. Per ulteriori informazioni, consulta la sezione Strutturare i dati per una coerenza elevata.

Proprietà e tipi di valore

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ù tipi e due entità possono avere valori di tipi diversi per la stessa proprietà. Le proprietà possono essere indicizzate o non indicizzate (le query che ordinano o filtrano in base a 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 di Go Ordinamento Note
Numero intero int
int8
int16
int32
int64
Numerico Numero intero a 64 bit con segno
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 più lunghi di 1500 byte generano un errore in fase di esecuzione.
Stringa (lunga) string (con noindex) Nessuno Fino a 1 megabyte

Non indicizzate
Byte slice (short) datastore.ByteString Ordine dei byte Fino a 1500 byte. I valori più lunghi di 1500 byte generano un errore in fase di esecuzione.
Slice di byte (long) []byte Nessuno Fino a 1 megabyte

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

Puoi anche utilizzare un struct o un slice per aggregare le proprietà. Per ulteriori dettagli, consulta la documentazione di riferimento di Datastore.

Quando una query coinvolge 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 dei byte (breve)
    • Stringa Unicode
    • Chiavi dell'archivio BLOB
  5. Numeri in virgola mobile
  6. Punti geografici
  7. Chiavi del datastore

Poiché le sezioni di byte lunghi e le stringhe lunghe non sono indicizzate, non è stato definito un ordinamento.

Utilizzo delle 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ò derivarla dalla chiave, dal tipo e dall'identificatore padre), può utilizzare la chiave per operare direttamente sull'entità. Un'applicazione può anche ottenere la chiave di un'entità come risultato di una query Datastore. Per ulteriori informazioni, consulta la pagina Query Datastore.

Creazione di un'entità

In Go, crei una nuova entità creando un'istanza di una struttura Go, completando i relativi campi e chiamando datastore.Put per salvarla in Datastore. In Datastore verranno salvati solo i campi esportati (che iniziano con una lettera maiuscola). 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 della chiave vuoto o utilizzi datastore.NewIncompleteKey, Datastore genera 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 richiama 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 eliminare l'entità 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. Permettono di agire su più entità in un'unica 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 modificano i costi. Ti verrà addebitato il costo di ogni chiave in un'operazione collettiva, indipendentemente dal fatto che esista o meno. Le dimensioni delle entità coinvolte in un'operazione non influiscono sul costo.