A API Async Datastore permite-lhe fazer chamadas paralelas e não bloqueadoras para o armazenamento de dados e obter os resultados destas chamadas num ponto posterior no processamento do pedido. Esta documentação descreve os seguintes aspetos da API Async Datastore:
Trabalhar com o serviço Async Datastore
Com a API Async Datastore, faz chamadas ao Datastore através de métodos da interface AsyncDatastoreService. Obtém este objeto chamando 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();
AsyncDatastoreService
suporta as mesmas operações que DatastoreService, exceto que a maioria dos métodos devolve imediatamente um Future cujo resultado pode bloquear num ponto posterior. Por exemplo, DatastoreService.get() devolve uma Entidade, mas AsyncDatastoreService.get() devolve um 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: as exceções não são lançadas até chamar o método get(). Chamar este método permite-lhe verificar se a operação assíncrona foi bem-sucedida.
Se tiver um AsyncDatastoreService
, mas precisar de executar uma operação de forma síncrona, invoque o método AsyncDatastoreService
adequado 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();
Trabalhar com transações assíncronas
As chamadas API de armazenamento de dados assíncronas podem participar em transações tal como as chamadas síncronas. Segue-se uma função que ajusta o salário de um Employee
e escreve uma entidade SalaryAdjustment
adicional no mesmo grupo de entidades que o Employee
, tudo numa ú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 }
Este exemplo ilustra uma diferença importante entre chamadas assíncronas sem transações e chamadas assíncronas com transações. Quando não está a usar uma transação, a única forma de garantir que uma chamada assíncrona individual foi concluída é obter o valor de retorno do Future
que foi devolvido quando a chamada foi feita. Quando usa uma transação, a chamada Transaction.commit() bloqueia o resultado de todas as chamadas assíncronas feitas desde o início da transação antes de a confirmar.
Assim, no exemplo acima, embora a nossa chamada assíncrona para inserir a entidade SalaryAdjustment
possa ainda estar pendente quando chamamos commit()
, a confirmação não ocorre até que a inserção seja concluída. Da mesma forma, se optar por chamar commitAsync()
em vez de commit()
, invocar get()
no Future
devolvido por commitAsync()
bloqueia até que todas as chamadas assíncronas pendentes sejam concluídas.
Nota: as transações estão associadas a uma discussão específica e não a uma instância específica de DatastoreService
ou AsyncDatastoreService
. Isto significa que, se iniciar uma transação com um DatastoreService
e executar uma chamada assíncrona com um AsyncDatastoreService
, a chamada assíncrona participa na transação. Em alternativa, para o dizer de forma mais concisa, DatastoreService.getCurrentTransaction()
e AsyncDatastoreService.getCurrentTransaction()
devolvem sempre o mesmo Transaction
.
Trabalhar com futuros
O Javadoc futuro explica a maioria do que precisa de saber para trabalhar com êxito com um Future
devolvido pela API Async Datastore, mas existem algumas coisas específicas do App Engine das quais tem de ter conhecimento:
- Quando chama Future.get(long timeout, TimeUnit unit), o limite de tempo é separado de qualquer prazo da RPC definido quando criou o
AsyncDatastoreService
. Para mais informações, consulte o artigo Consistência de dados em consultas do Cloud Datastore. - Quando chama Future.cancel(boolean mayInterruptIfRunning) e essa chamada devolve
true
, isso não significa necessariamente que o estado do seu repositório de dados não foi alterado. Por outras palavras, cancelar umaFuture
não é o mesmo que reverter uma transação.
Consultas assíncronas
Atualmente, não expomos uma API explicitamente assíncrona para consultas. No entanto, quando invoca PreparedQuery.asIterable(), PreparedQuery.asIterator() ou PreparedQuery.asList(FetchOptions fetchOptions), o DatastoreService e o AsyncDatastoreService são devolvidos imediatamente e pré-buscam os resultados de forma assíncrona. Isto permite que a sua aplicação execute tarefas em paralelo enquanto os resultados da consulta são obtidos.
// ... 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 de armazenamento de dados assíncronas
As operações expostas pela interface DatastoreService são síncronas. Por exemplo, quando chama DatastoreService.get()
, o seu código é bloqueado até que a chamada para o arquivo de dados seja concluída. Se a única coisa que a sua aplicação precisa de fazer é renderizar o resultado do get()
em HTML, bloquear até que a chamada esteja concluída é perfeitamente razoável. No entanto, se a sua aplicação precisar do resultado do get()
mais o resultado de um Query
para renderizar a resposta e se o get()
e o Query
não tiverem dependências de dados, então esperar até que o get()
seja concluído para iniciar o Query
é uma perda de tempo. Segue-se um exemplo de código que pode ser melhorado através da API assí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);
Em vez de aguardar a conclusão da 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íncronas e assíncronas deste código usam quantidades semelhantes de CPU (afinal, ambas realizam a mesma quantidade de trabalho), mas, uma vez que a versão assíncrona permite que as duas operações da base de dados sejam executadas em paralelo, a versão assíncrona tem uma latência inferior. Em geral, se precisar de realizar várias operações de armazenamento de dados que não tenham dependências de dados, o AsyncDatastoreService
pode melhorar significativamente a latência.