Como trabalhar com operações de longa duração

Às vezes, uma API pode expor um método que leva um tempo significativo para ser concluído. Em vez de bloquear enquanto a tarefa é executada, retorne uma promessa e deixe o usuário verificar o status.

As bibliotecas de cliente Google Cloud para Rust fornecem helpers para trabalhar com essas operações de longa duração (LROs, na sigla em inglês). Este guia mostra como iniciar LROs e aguardar a conclusão delas.

Pré-requisitos

Este guia usa o serviço Cloud Storage para manter os snippets de código concretos. Esses conceitos se aplicam a qualquer outro serviço que use LROs.

Antes de seguir este guia, você precisa:

Para instruções completas de configuração das bibliotecas Rust, consulte Como configurar seu ambiente de desenvolvimento.

Dependências

Declare as dependências Google Cloud no arquivo Cargo.toml:

cargo add google-cloud-storage google-cloud-lro google-cloud-longrunning

Você também precisa de vários recursos do tokio:

cargo add tokio --features full,macros

Iniciar uma operação de longa duração

Este exemplo usa Renomear pasta. Essa operação pode levar muito tempo para pastas grandes, mas é relativamente rápida para pastas menores.

Para iniciar uma operação de longa duração, inicialize um cliente e faça a RPC.

Primeiro, adicione declarações use para evitar nomes de pacotes longos:

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

Em seguida, crie o cliente:

let client = StorageControl::builder().build().await?;

Nas bibliotecas de cliente Rust, cada solicitação é representada por um método que retorna um criador de solicitações. Chame o método no cliente para criar o criador de solicitações:

let operation = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

As funções de exemplo aceitam os nomes de bucket e pasta como argumentos:

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {

Faça a solicitação e aguarde o retorno de uma Operation. Esse Operation funciona como uma promessa para o resultado da solicitação de longa duração:

    let operation =
        // ...
        .send()
        .await?;

Essa solicitação inicia a operação em segundo plano. Aguarde a conclusão da operação para determinar se ela foi bem-sucedida.

Pesquisar automaticamente uma operação de longa duração

Para configurar a pesquisa automática, inicie uma operação de longa duração com um Poller em vez de .send().wait, assim:

.poller()
.until_done()
.await?;

Primeiro, introduza a característica Poller no escopo usando uma declaração use:

use google_cloud_lro::Poller;

Em seguida, inicialize o cliente e prepare a solicitação como antes:

let response = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

Pesquise até que a operação seja concluída e imprima o resultado:

    .poller()
    .until_done()
    .await?;

println!("LRO completed, response={response:?}");

Pesquisar uma operação de longa duração com resultados intermediários

O método .until_done() é conveniente, mas omite relatórios de progresso parciais de operações de longa duração. Se o aplicativo exigir essas informações, use o poller diretamente:

    let mut poller = client
        .rename_folder()
        /* more stuff */
        .poller();

Em seguida, use o poller em um loop:

while let Some(p) = poller.poll().await {
    match p {
        PollingResult::Completed(r) => {
            println!("LRO completed, response={r:?}");
        }
        PollingResult::InProgress(m) => {
            println!("LRO in progress, metadata={m:?}");
        }
        PollingResult::PollingError(e) => {
            println!("Transient error polling the LRO: {e}");
        }
    }
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}

Esse loop aguarda explicitamente antes de fazer a pesquisa novamente. O período de pesquisa depende da operação específica e da carga útil dela. Consulte a documentação do serviço ou faça testes com seus dados para determinar um bom valor.

Pesquisar manualmente uma operação de longa duração

Recomendamos abordagens de pesquisa automatizadas, mas também é possível pesquisar manualmente uma operação de longa duração. Para mais informações, consulte a documentação de referência da mensagem Operation.

Inicie a operação de longa duração usando o cliente:

    let mut operation = client
        .rename_folder()
        /* more stuff */
        .send()
        .await?;

Inicie um loop de polling e verifique se a operação foi concluída usando o campo done:

let response: anyhow::Result<Folder> = loop {
    if operation.done {

Quando a operação é concluída, ela geralmente contém um resultado. O campo de resultado é opcional porque o serviço pode retornar done como verdadeiro sem um resultado. Por exemplo, uma operação de exclusão bem-sucedida não tem valor de retorno. Neste exemplo, o serviço do Cloud Storage sempre retorna um valor:

match &operation.result {
    None => {
        break Err(anyhow!("missing result for finished operation"));
    }

Uma operação iniciada pode não ser concluída com sucesso. O resultado pode ser um erro ou uma resposta válida. Primeiro, verifique se há erros:

Some(r) => {
    break match r {
        longrunning::model::operation::Result::Error(s) => {
            Err(anyhow!("operation completed with error {s:?}"))
        }

O tipo de erro é um tipo de mensagem Status. Isso NÃO implementa a característica padrão Error. Converta manualmente para um erro válido usando Error::service.

Se o resultado for bem-sucedido, extraia o tipo de resposta. Encontre esse tipo na documentação do método LRO ou na documentação da API do serviço:

longrunning::model::operation::Result::Response(any) => {
    let response = any.to_msg::<Folder>()?;
    Ok(response)
}

A extração do valor pode falhar se o tipo não corresponder ao que o serviço enviou.

Os tipos deGoogle Cloud podem adicionar campos e ramificações no futuro. As bibliotecas de cliente Google Cloudpara Rust marcam todas as structs e enums como #[non_exhaustive]. Trate este caso:

_ => Err(anyhow!("unexpected result branch {r:?}")),

Se a operação não tiver sido concluída, ela poderá conter metadados. Alguns serviços incluem informações iniciais sobre a solicitação, enquanto outros incluem relatórios parciais de progresso. É possível extrair e gerar relatórios com estes metadados:

if let Some(any) = &operation.metadata {
    let metadata = any.to_msg::<RenameFolderMetadata>()?;
    println!("LRO in progress, metadata={metadata:?}");
}

Aguarde antes de pesquisar novamente. Considere ajustar o período de pesquisa usando espera exponencial truncada. Este exemplo faz pesquisas a cada 500 ms:

tokio::time::sleep(std::time::Duration::from_millis(500)).await;

Consulte o status da operação:

if let Ok(attempt) = client
    .get_operation()
    .set_name(&operation.name)
    .send()
    .await
{
    operation = attempt;
}

Para simplificar, este exemplo ignora todos os erros. No seu aplicativo, você pode tratar um subconjunto de erros como não recuperáveis e limitar o número de tentativas de sondagem.

A seguir

  • Confira o código-fonte dos exemplos no GitHub.