API Async Datastore

L'API Async Datastore consente di effettuare chiamate parallele e non bloccanti al datastore e di recuperare i risultati di queste chiamate in un secondo momento durante la gestione della richiesta. Questa documentazione descrive i seguenti aspetti dell'API Async Datastore:

Utilizzo del servizio Async Datastore

Con l'API Async Datastore, effettui chiamate Datastore utilizzando i metodi dell'interfaccia AsyncDatastoreService. Ottieni questo oggetto chiamando il metodo della classe getAsyncDatastoreService() della classe DatastoreServiceFactory.

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;

// ...
AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();

AsyncDatastoreService supporta le stesse operazioni di DatastoreService, ad eccezione della maggior parte dei metodi che restituiscono immediatamente un Future il cui risultato può essere bloccato in un secondo momento. Ad esempio, DatastoreService.get() restituisce un'entità, mentre AsyncDatastoreService.get() restituisce un Future<Entity>.

// ...

Key key = KeyFactory.createKey("Employee", "Max");
// Async call returns immediately
Future<Entity> entityFuture = datastore.get(key);

// Do other stuff while the get operation runs in the background...

// Blocks if the get operation has not finished, otherwise returns instantly
Entity entity = entityFuture.get();

Nota: le eccezioni non vengono generate finché non chiami il metodo get(). La chiamata di questo metodo consente di verificare che l'operazione asincrona sia andata a buon fine.

Se hai un AsyncDatastoreService, ma devi eseguire un'operazione in modo sincrono, richiama il metodo AsyncDatastoreService appropriato e blocca immediatamente il risultato:

// ...

Entity entity = new Employee("Employee", "Alfred");
// ... populate entity properties

// Make a sync call via the async interface
Key key = datastore.put(key).get();

Utilizzo delle transazioni asincrone

Le chiamate API Async Datastore possono partecipare alle transazioni proprio come le chiamate sincrone. Ecco una funzione che regola lo stipendio di un Employee e scrive un'entità SalaryAdjustment aggiuntiva nello stesso gruppo di entità di Employee, il tutto in un'unica transazione.

void giveRaise(AsyncDatastoreService datastore, Key employeeKey, long raiseAmount)
        throws Exception {
    Future<Transaction> txn = datastore.beginTransaction();

    // Async call to lookup the Employee entity
    Future<Entity> employeeEntityFuture = datastore.get(employeeKey);

    // Create and put a SalaryAdjustment entity in parallel with the lookup
    Entity adjustmentEntity = new Entity("SalaryAdjustment", employeeKey);
    adjustmentEntity.setProperty("adjustment", raiseAmount);
    adjustmentEntity.setProperty("adjustmentDate", new Date());
    datastore.put(adjustmentEntity);

    // Fetch the result of our lookup to make the salary adjustment
    Entity employeeEntity = employeeEntityFuture.get();
    long salary = (Long) employeeEntity.getProperty("salary");
    employeeEntity.setProperty("salary", salary + raiseAmount);

    // Re-put the Employee entity with the adjusted salary.
    datastore.put(employeeEntity);
    txn.get().commit(); // could also call txn.get().commitAsync() here
}

Questo esempio illustra un'importante differenza tra le chiamate asincrone senza transazioni e le chiamate asincrone con transazioni. Quando non utilizzi una transazione, l'unico modo per assicurarti che una singola chiamata asincrona sia stata completata è recuperare il valore restituito di Future restituito al momento della chiamata. Quando utilizzi una transazione, la chiamata a Transaction.commit() blocca il risultato di tutte le chiamate asincrone effettuate dall'inizio della transazione prima di eseguirne il commit.

Quindi, nel nostro esempio precedente, anche se la chiamata asincrona per inserire l'entità SalaryAdjustment potrebbe essere ancora in sospeso quando chiamiamo commit(), il commit non verrà eseguito finché l'inserimento non sarà completato. Allo stesso modo, se scegli di chiamare commitAsync() anziché commit(), l'invocazione di get() su Future restituito da commitAsync() si blocca finché tutte le chiamate asincrone in sospeso non sono state completate.

Nota:le transazioni sono associate a un thread specifico, non a un'istanza specifica di DatastoreService o AsyncDatastoreService. Ciò significa che se avvii una transazione con un DatastoreService ed esegui una chiamata asincrona con un AsyncDatastoreService, la chiamata asincrona partecipa alla transazione. In altre parole, DatastoreService.getCurrentTransaction() e AsyncDatastoreService.getCurrentTransaction() restituiscono sempre lo stesso Transaction.

Utilizzo dei futures

La documentazione Javadoc futura spiega la maggior parte di ciò che devi sapere per lavorare correttamente con un Future restituito dall'API Async Datastore, ma ci sono alcune cose specifiche di App Engine di cui devi essere a conoscenza:

Query asincrone

Al momento non esponiamo un'API esplicitamente asincrona per le query. Tuttavia, quando richiami PreparedQuery.asIterable(), PreparedQuery.asIterator() o PreparedQuery.asList(FetchOptions fetchOptions), sia DatastoreService che AsyncDatastoreService vengono restituiti immediatamente e precaricano i risultati in modo asincrono. In questo modo, l'applicazione può eseguire il lavoro in parallelo durante il recupero dei risultati della query.

// ...

Query q1 = new Query("Salesperson");
q1.setFilter(new FilterPredicate("dateOfHire", FilterOperator.LESS_THAN, oneMonthAgo));

// Returns instantly, query is executing in the background.
Iterable<Entity> recentHires = datastore.prepare(q1).asIterable();

Query q2 = new Query("Customer");
q2.setFilter(new FilterPredicate("lastContact", FilterOperator.GREATER_THAN, oneYearAgo));

// Also returns instantly, query is executing in the background.
Iterable<Entity> needsFollowup = datastore.prepare(q2).asIterable();

schedulePhoneCall(recentHires, needsFollowUp);

Quando utilizzare le chiamate Async Datastore

Le operazioni esposte dall'interfaccia DatastoreService sono sincrone. Ad esempio, quando chiami DatastoreService.get(), il codice viene bloccato fino al completamento della chiamata al datastore. Se l'unica cosa che deve fare la tua applicazione è visualizzare il risultato di get() in HTML, bloccare fino al completamento della chiamata è una cosa perfettamente ragionevole da fare. Tuttavia, se la tua applicazione ha bisogno del risultato di get() più il risultato di Query per visualizzare la risposta e se get() e Query non hanno dipendenze dai dati, attendere il completamento di get() per avviare Query è una perdita di tempo. Ecco un esempio di codice che può essere migliorato utilizzando l'API asincrona:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Key empKey = KeyFactory.createKey("Employee", "Max");

// Read employee data from the Datastore
Entity employee = datastore.get(empKey); // Blocking for no good reason!

// Fetch payment history
Query query = new Query("PaymentHistory");
PreparedQuery pq = datastore.prepare(query);
List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10));
renderHtml(employee, result);

Invece di attendere il completamento di get(), utilizza un'istanza di AsyncDatastoreService per eseguire la chiamata in modo asincrono:

AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
Key empKey = KeyFactory.createKey("Employee", "Max");

// Read employee data from the Datastore
Future<Entity> employeeFuture = datastore.get(empKey); // Returns immediately!

// Fetch payment history for the employee
Query query = new Query("PaymentHistory", empKey);
PreparedQuery pq = datastore.prepare(query);

// Run the query while the employee is being fetched
List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10));
// Implicitly performs query asynchronously
Entity employee = employeeFuture.get(); // Blocking!
renderHtml(employee, result); 

Le versioni sincrona e asincrona di questo codice utilizzano quantità simili di CPU (dopo tutto, entrambe eseguono la stessa quantità di lavoro), ma poiché la versione asincrona consente l'esecuzione parallela delle due operazioni del datastore, la latenza è inferiore. In generale, se devi eseguire più operazioni Datastore che non hanno dipendenze dai dati, AsyncDatastoreService può migliorare significativamente la latenza.