API de Async Datastore

La API de Async Datastore permite realizar llamadas paralelas y sin bloqueo al almacén de datos y recuperar los resultados de estas llamadas más adelante. En esta documentación, se describen los siguientes aspectos de la API de Async Datastore:

Trabaja con el servicio de Async Datastore

Con la API de Async Datastore, puedes realizar llamadas al almacén de datos mediante métodos de la interfaz AsyncDatastoreService. Este objeto se obtiene cuando llamas al método de clase getAsyncDatastoreService() de la clase DatastoreServiceFactory.

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

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

AsyncDatastoreService admite las mismas operaciones que DatastoreService, pero la mayoría de los métodos muestran un objeto Future de inmediato, cuyo resultado puedes bloquear más adelante. Por ejemplo, DatastoreService.get() muestra una Entidad, pero AsyncDatastoreService.get() muestra una 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: No se arrojarán excepciones hasta que llames al método get(). Si llamas a este método, podrás verificar si la operación asíncrona tuvo éxito.

Si tienes un AsyncDatastoreService pero necesitas ejecutar una operación de forma síncrona, invoca el método AsyncDatastoreService adecuado y, luego, bloquea el resultado de inmediato:

// ...

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

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

Trabaja con transacciones asíncronas

Las llamadas a la API de Async Datastore pueden participar en transacciones al igual que las llamadas síncronas. Aquí te mostramos una función que ajusta el sueldo de un Employee y escribe una entidad SalaryAdjustment adicional en el mismo grupo de entidades que el Employee, todo dentro de una misma transacción.

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
}

En este ejemplo, se muestra una diferencia importante entre las llamadas asíncronas con y sin transacciones. Cuando no usas una transacción, la única forma de garantizar que una llamada asíncrona individual se haya completado es recuperar el valor de retorno de Future que se mostró cuando se realizó la llamada. Cuando usas una transacción, si llamas a Transaction.commit(), se bloquea el resultado de todas las llamadas asíncronas realizadas desde que comenzó la transacción antes de confirmarla.

Por lo tanto, en nuestro ejemplo anterior, a pesar de que la llamada asíncrona para insertar la entidad SalaryAdjustment aún puede estar pendiente cuando llamamos a commit(), la confirmación no se producirá hasta que la inserción se complete. Del mismo modo, si eliges llamar a commitAsync() en lugar de commit(), la invocación de get() en Future que muestra commitAsync() se bloquea hasta que todas las llamadas asíncronas pendientes se completen.

Nota: Las transacciones se asocian a un subproceso específico, no a una instancia específica de DatastoreService o AsyncDatastoreService. Esto significa que si inicias una transacción con un DatastoreService y realizas una llamada asíncrona con un AsyncDatastoreService, la llamada asíncrona participa en la transacción. Para decirlo de forma más concisa, DatastoreService.getCurrentTransaction() y AsyncDatastoreService.getCurrentTransaction() siempre muestran la misma Transaction.

Trabaja con Futures

En el javadoc de Future, se explica casi todo lo que debes saber para trabajar de forma correcta con un Future que muestra la API de Async Datastore, pero debes tener en cuenta algunos aspectos específicos de App Engine:

Consultas asíncronas

Por el momento, no exponemos una API asíncrona de forma explícita para consultas. Sin embargo, cuando invocas a PreparedQuery.asIterable(), PreparedQuery.asIterator() o PreparedQuery.asList(FetchOptions fetchOptions), DatastoreService y AsyncDatastoreService muestran y recuperan los resultados de forma inmediata y asíncrona. Esto permite a tu aplicación trabajar en paralelo mientras se recuperan los resultados de la consulta.

// ...

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);

Cuándo usar llamadas de Async Datastore

Las operaciones que expone la interfaz DatastoreService son síncronas. Por ejemplo, cuando llamas a DatastoreService.get(), tu código se bloqueará hasta que la llamada al almacén de datos se complete. Si lo único que tu aplicación necesita hacer es procesar el resultado de get() en HTML, lo más razonable es que el bloqueo permanezca hasta que la llamada se haya completado. Sin embargo, si la aplicación necesita el resultado de get() además del resultado de una Query a fin de procesar la respuesta, y si get() y Query no tienen ninguna dependencia de datos, es una pérdida de tiempo esperar hasta que get() se complete para iniciar la Query. Aquí mostramos un ejemplo de un código que se puede mejorar mediante el uso de la API asíncrona:

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);

En lugar de esperar a que get() se complete, usa una instancia de AsyncDatastoreService para ejecutar la llamada de forma asíncrona:

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); 

Las versiones síncronas y asíncronas de este código usan cantidades similares de CPU (ya que ambas realizan la misma cantidad de trabajo), pero, debido a que la versión asíncrona permite que las dos operaciones del almacén de datos se ejecuten en paralelo, la versión asíncrona tiene una latencia menor. En general, si necesitas realizar varias operaciones de almacén de datos que no tienen dependencias de datos, AsyncDatastoreService puede mejorar bastante la latencia.