A API Async Datastore permite realizar chamadas paralelas e sem bloqueio ao armazenamento de dados e recuperar os resultados dessas chamadas em um ponto posterior no gerenciamento da solicitação. Esta documentação descreve os seguintes aspectos da API Async Datastore:
Como trabalhar com o serviço de armazenamento de dados assíncrono
Com a Async Datastore API, você faz chamadas de armazenamento de dados usando métodos da interface AsyncDatastoreService. Esse objeto é recebido ao chamar o método de classe getAsyncDatastoreService() da classe DatastoreServiceFactory.
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; // ... AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
A AsyncDatastoreService
aceita as mesmas operações que DatastoreService, exceto que a maioria dos métodos retorna imediatamente uma classe Future (em inglês). É possível bloquear o resultado dela posteriormente. Por exemplo, DatastoreService.get() retorna uma classe Entity, mas AsyncDatastoreService.get() retorna 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();
Observação: as exceções não são geradas até que você chame o método get(). A chamada a esse método permite confirmar o sucesso da operação assíncrona.
Se você tem um AsyncDatastoreService
, mas precisa executar uma operação de maneira síncrona, invoque o método AsyncDatastoreService
apropriado e, em seguida, bloqueie imediatamente o resultado:
// ... Entity entity = new Employee("Employee", "Alfred"); // ... populate entity properties // Make a sync call via the async interface Key key = datastore.put(key).get();
Como trabalhar com transações assíncronas
Chamadas da API assíncrona do armazenamento de dados podem participar de transações da mesma forma que chamadas síncronas. Esta é uma função que ajusta o salário de um Employee
e grava uma outra entidade SalaryAdjustment
no mesmo grupo de entidades que o Employee
, tudo dentro de uma única transação.
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 }
Nesse exemplo, ilustramos uma diferença importante entre as chamadas assíncronas sem transações e aquelas com transações. Quando você não está usando uma transação, a única maneira de confirmar se uma chamada assíncrona individual foi concluída é buscar o valor de Future
retornado quando a chamada foi feita. Quando você está usando uma transação, chamar Transaction.commit() bloqueia o resultado de todas as chamadas assíncronas feitas desde que a transação foi iniciada antes de confirmá-la.
Então, no exemplo acima, mesmo que nossa chamada assíncrona para inserir a entidade SalaryAdjustment
ainda possa estar pendente quando chamamos commit()
, a execução ocorrerá somente depois que a inserção for concluída. Da mesma forma, se você optar por chamar commitAsync()
em vez de commit()
, invocar get()
na classe Future
retornada por commitAsync()
gera bloqueio até que todas as chamadas assíncronas pendentes tenham sido concluídas.
Observação: transações são associadas a uma sequência específica e não a uma instância específica de DatastoreService
ou AsyncDatastoreService
. Isso significa que, se você iniciar uma transação com um DatastoreService
e realizar uma chamada assíncrona com um AsyncDatastoreService
, a chamada assíncrona participará da transação. Em resumo, DatastoreService.getCurrentTransaction()
e AsyncDatastoreService.getCurrentTransaction()
sempre retornam a mesma Transaction
.
Como trabalhar com classes Future
O documento sobre a classe Future do Java (em inglês) explica grande parte do que você precisa saber para trabalhar com uma classe Future
retornada pela API Async Datastore, mas é necessário observar alguns fatores específicos do App Engine:
- Quando você chama Future.get(long timeout, TimeUnit unit), o tempo limite é separado de qualquer prazo de RPC definido na criação de
AsyncDatastoreService
. Para mais informações, consulte Consistência de dados em consultas do Cloud Datastore. - Quando você chama Future.cancel(boolean mayInterruptIfRunning), e essa chamada retorna
true
, isso não significa necessariamente que o estado do armazenamento de dados está inalterado. Em outras palavras, cancelar umFuture
não é o mesmo que reverter uma transação.
Consultas assíncronas
Atualmente, não oferecemos uma API explicitamente assíncrona para consultas. No entanto, quando você invoca PreparedQuery.asIterable(), PreparedQuery.asIterator() ou PreparedQuery.asList(FetchOptions fetchOptions), tanto DatastoreService quanto AsyncDatastoreService imediatamente retornam resultados de busca prévia de forma assíncrona. Isso permite que o aplicativo realize trabalho paralelamente enquanto os resultados da consulta são buscados.
// ... 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 usar chamadas assíncronas ao armazenamento de dados
As operações expostas pela interface DatastoreService são síncronas. Por exemplo, quando você chama DatastoreService.get()
, seu código bloqueia até que a chamada ao armazenamento de dados seja concluída. Se a única coisa que seu aplicativo precisa fazer é exibir o resultado do get()
em HTML, o bloqueio até que a chamada seja concluída é uma medida perfeitamente razoável a se tomar. No entanto, caso seu aplicativo precise do resultado get()
, além do resultado de uma consulta Query
para renderizar a resposta, e o get()
e a consulta Query
não tenham dependências de dados, será perda de tempo aguardar até que o get()
seja concluído para iniciar a consulta Query
. Veja um exemplo de código que pode ser melhorado com o uso da Async API:
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);
Em vez de aguardar a conclusão de get()
, use uma instância de AsyncDatastoreService para executar a chamada de forma assí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);
As versões síncrona e assíncrona desse código usam quantidades de CPU semelhantes. Afinal, as duas realizam a mesma quantidade de trabalho. Porém, por permitir que as duas operações do armazenamento de dados sejam executadas paralelamente, a versão assíncrona tem latência mais baixa. De modo geral, se você precisa realizar várias operações do armazenamento de dados que não têm dependências de dados, o AsyncDatastoreService
pode melhorar a latência significativamente.