Processamento em segundo plano

Para muitos aplicativos, é necessário fazer o processamento em segundo plano fora do contexto de uma solicitação da Web. Este tutorial cria um app da Web que permite aos usuários inserir texto para traduzir e, em seguida, exibe uma lista de traduções anteriores. A tradução é feita em um processo em segundo plano para evitar o bloqueio da solicitação do usuário.

O diagrama a seguir ilustra o processo de solicitação da tradução.

Diagrama da arquitetura.

Aqui está a sequência de eventos sobre como o tutorial do app funciona:

  1. Acesse a página da Web para ver uma lista de traduções anteriores armazenadas no Firestore.
  2. Solicite uma tradução de texto inserindo um formulário HTML.
  3. A solicitação de tradução é publicada no Pub/Sub.
  4. Um serviço do Cloud Run inscrito nesse tópico Pub/Sub é acionado.
  5. O serviço do Cloud Run usa o Cloud Translation para traduzir o texto.
  6. O serviço do Cloud Run armazena o resultado no Firestore.

Este tutorial se destina a qualquer pessoa interessada em aprender sobre o processamento em segundo plano com o Google Cloud. Nenhuma experiência anterior é necessária com Pub/Sub, Firestore, Cloud Run. No entanto, para entender todo o código, é útil alguma experiência com Java e HTML.

Objetivos

  • Entenda e implemente um serviço do Cloud Run.
  • Testar o app.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Ao concluir as tarefas descritas neste documento, é possível evitar o faturamento contínuo excluindo os recursos criados. Saiba mais em Limpeza.

Antes de começar

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  4. Ative as APIs Firestore, Pub/Sub, and Cloud Translation.

    Ative as APIs

  5. Instale a CLI do Google Cloud.
  6. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  7. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  8. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  9. Ative as APIs Firestore, Pub/Sub, and Cloud Translation.

    Ative as APIs

  10. Instale a CLI do Google Cloud.
  11. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  12. Atualize os componentes gcloud:
    gcloud components update
  13. Prepare seu ambiente de desenvolvimento.

    Acessar o guia de configuração do Java

Como preparar o aplicativo

  1. Na janela de terminal, clone o repositório do app de amostra em sua máquina local:

    git clone https://github.com/GoogleCloudPlatform/getting-started-java.git

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

  2. Mude para o diretório que contém o código de amostra do processamento em segundo plano:

    cd getting-started-java/background

Noções básicas sobre o app

Há dois componentes principais para o app da Web:

  • Um servidor HTTP Java para gerenciar solicitações da Web. O servidor tem os dois endpoints a seguir:
    • /translate
      • GET (usando um navegador da Web): exibe as 10 solicitações de tradução processada mais recentes enviadas pelos usuários.
      • POST (com uma assinatura do Pub/Sub): processa solicitações de tradução usando a API Cloud Translation e armazena os resultados no Firestore.
    • /create: o formulário para enviar novas solicitações de conversão.
  • Atender clientes que processam as solicitações de tradução enviadas pelo formulário da Web. Existem três clientes que trabalham juntos:
    • Pub/Sub: quando o formulário da Web é enviado por um usuário, o cliente do Pub/Sub publica uma mensagem com os detalhes da solicitação. Uma assinatura criada neste tutorial retransmite essas mensagens para o terminal Cloud Run que você cria para executar traduções.
    • Translation: este cliente lida com solicitações de Pub/Sub executando as traduções.
    • Firestore: Quando a tradução é concluída, este cliente armazena os dados da solicitação junto com a tradução no Firestore. Esse cliente também lê as solicitações mais recentes no terminal /translate principal.

Como entender o código do Cloud Run

  • O aplicativo Cloud Run tem dependências no Firestore, Translation e Pub/Sub.

    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-firestore</artifactId>
      <version>3.0.16</version>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-translate</artifactId>
      <version>2.1.1</version>
    </dependency>
    
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-pubsub</artifactId>
      <version>1.113.7</version>
    </dependency>
  • Os clientes globais do Firestore, Translation e Pub/Sub são inicializados para que possam ser reutilizados entre invocações. Dessa forma, você não precisa inicializar novos clientes para cada chamada, o que atrasaria a execução.

    @WebListener("Creates Firestore and TranslateServlet service clients for reuse between requests.")
    public class BackgroundContextListener implements ServletContextListener {
      @Override
      public void contextDestroyed(javax.servlet.ServletContextEvent event) {}
    
      @Override
      public void contextInitialized(ServletContextEvent event) {
        String firestoreProjectId = System.getenv("FIRESTORE_CLOUD_PROJECT");
        Firestore firestore = (Firestore) event.getServletContext().getAttribute("firestore");
        if (firestore == null) {
          firestore =
              FirestoreOptions.getDefaultInstance().toBuilder()
                  .setProjectId(firestoreProjectId)
                  .build()
                  .getService();
          event.getServletContext().setAttribute("firestore", firestore);
        }
    
        Translate translate = (Translate) event.getServletContext().getAttribute("translate");
        if (translate == null) {
          translate = TranslateOptions.getDefaultInstance().getService();
          event.getServletContext().setAttribute("translate", translate);
        }
    
        String topicId = System.getenv("PUBSUB_TOPIC");
        TopicName topicName = TopicName.of(firestoreProjectId, topicId);
        Publisher publisher = (Publisher) event.getServletContext().getAttribute("publisher");
        if (publisher == null) {
          try {
            publisher = Publisher.newBuilder(topicName).build();
            event.getServletContext().setAttribute("publisher", publisher);
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
  • O gerenciador de índice (/) obtém todas as traduções existentes do Firestore e preenche um modelo de HTML com a lista:

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
      Firestore firestore = (Firestore) this.getServletContext().getAttribute("firestore");
      CollectionReference translations = firestore.collection("translations");
      QuerySnapshot snapshot;
      try {
        snapshot = translations.limit(10).get().get();
      } catch (InterruptedException | ExecutionException e) {
        throw new ServletException("Exception retrieving documents from Firestore.", e);
      }
      List<TranslateMessage> translateMessages = Lists.newArrayList();
      List<QueryDocumentSnapshot> documents = Lists.newArrayList(snapshot.getDocuments());
      documents.sort(Comparator.comparing(DocumentSnapshot::getCreateTime));
    
      for (DocumentSnapshot document : Lists.reverse(documents)) {
        String encoded = gson.toJson(document.getData());
        TranslateMessage message = gson.fromJson(encoded, TranslateMessage.class);
        message.setData(decode(message.getData()));
        translateMessages.add(message);
      }
      req.setAttribute("messages", translateMessages);
      req.setAttribute("page", "list");
      req.getRequestDispatcher("/base.jsp").forward(req, resp);
    }
  • Novas traduções são solicitadas através do envio de um formulário HTML. O gerenciador de tradução de solicitação, registrado em /create, analisa o envio do formulário, valida a solicitação e publica uma mensagem no Pub/Sub:

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
      String text = req.getParameter("data");
      String sourceLang = req.getParameter("sourceLang");
      String targetLang = req.getParameter("targetLang");
    
      Enumeration<String> paramNames = req.getParameterNames();
      while (paramNames.hasMoreElements()) {
        String paramName = paramNames.nextElement();
        logger.warning("Param name: " + paramName + " = " + req.getParameter(paramName));
      }
    
      Publisher publisher = (Publisher) getServletContext().getAttribute("publisher");
    
      PubsubMessage pubsubMessage =
          PubsubMessage.newBuilder()
              .setData(ByteString.copyFromUtf8(text))
              .putAttributes("sourceLang", sourceLang)
              .putAttributes("targetLang", targetLang)
              .build();
    
      try {
        publisher.publish(pubsubMessage).get();
      } catch (InterruptedException | ExecutionException e) {
        throw new ServletException("Exception publishing message to topic.", e);
      }
    
      resp.sendRedirect("/");
    }
  • A assinatura do Pub/Sub que você cria encaminha essas solicitações para o terminal Cloud Run, que analisa a mensagem do Pub/Sub para obter o texto a ser traduzido e o idioma de destino desejado. A API de tradução converte a sequência no idioma selecionado.

    String body = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
    
    PubSubMessage pubsubMessage = gson.fromJson(body, PubSubMessage.class);
    TranslateMessage message = pubsubMessage.getMessage();
    
    // Use Translate service client to translate the message.
    Translate translate = (Translate) this.getServletContext().getAttribute("translate");
    message.setData(decode(message.getData()));
    Translation translation =
        translate.translate(
            message.getData(),
            Translate.TranslateOption.sourceLanguage(message.getAttributes().getSourceLang()),
            Translate.TranslateOption.targetLanguage(message.getAttributes().getTargetLang()));
  • O aplicativo armazena os dados de tradução em um novo documento criado no Firestore.

    // Use Firestore service client to store the translation in Firestore.
    Firestore firestore = (Firestore) this.getServletContext().getAttribute("firestore");
    
    CollectionReference translations = firestore.collection("translations");
    
    ApiFuture<WriteResult> setFuture = translations.document().set(message, SetOptions.merge());
    
    setFuture.get();
    resp.getWriter().write(translation.getTranslatedText());

Como implantar o aplicativo do Cloud Run

  1. Escolha um Nome de tópico do Pub/Sub secundária e gere um token de verificação de publicação/publicação secundária usando uuidgen ou um gerador UUID on-line, como uuidgenerator.net. Esse token garantirá que o ponto de extremidade do Cloud Run aceite apenas solicitações da assinatura do Pub/Sub que você criar.

    export PUBSUB_TOPIC=background-translate
    export PUBSUB_VERIFICATION_TOKEN=your-verification-token
    
  2. Crie um tópico do Pub/Sub:

     gcloud pubsub topics create $PUBSUB_TOPIC
    
    • Substitua MY_PROJECT no arquivo pom.xml pelo seu ID do projeto do Cloud.
  3. Crie e implemente uma imagem do seu código no GCR (um repositório de imagens) com o plug-in Maven do Jib.

     mvn clean package jib:build
    
  4. Implante o app no Cloud Run:

    gcloud run deploy background --image gcr.io/MY_PROJECT/background \
          --platform managed --region us-central1 --memory 512M \
          --update-env-vars PUBSUB_TOPIC=$PUBSUB_TOPIC,PUBSUB_VERIFICATION_TOKEN=$PUBSUB_VERIFICATION_TOKEN
    

    Onde MY_PROJECT é o nome do projeto Cloud que você criou. Este comando gera o terminal para o qual sua assinatura do Pub/Sub envia solicitações de tradução. Anote esse terminal, pois você precisará criar a assinatura do Pub/Sub e o visitará em um navegador para solicitar uma nova tradução.

Como testar o app

Depois de implantar o serviço do Cloud Run, tente solicitar uma tradução.

  1. Para visualizar o aplicativo no seu navegador, acesse o ponto de extremidade do Cloud Run que você criou anteriormente.

    Há uma página com uma lista vazia de traduções e um formulário para solicitar novas traduções.

  2. Clique em + Solicitar tradução, preencha o formulário de solicitação e clique em Enviar.

  3. O envio o leva automaticamente de volta ao caminho /translate, mas a nova tradução pode ainda não aparecer. Para atualizar a página, clique em Atualizar . Há uma nova linha na lista de tradução. Se você não vir uma tradução, aguarde mais alguns segundos e tente novamente. Se você ainda não vir uma tradução, consulte a próxima seção sobre como depurar o aplicativo.

Como depurar o aplicativo

Se você não conseguir se conectar ao seu serviço do Cloud Run ou não vir novas traduções, verifique o seguinte:

  • Verifique se o comando gcloud run deploy foi concluído com êxito e não gerou erros. Se houver erros (por exemplo, message=Build failed), corrija-os e tente executar novamente.

  • Verifique se há erros nos registros:

    1. No Console do Google Cloud, acesse a página do Cloud Run.

      Acessar a página do Cloud Run

    2. Clique no nome do serviço, background.

    3. Clique em Registros.

Limpar

Excluir o projeto

  1. No Console do Google Cloud, acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Exclua os serviços do Cloud Run.

  • Exclua os serviços do Cloud Run que você criou neste tutorial:

    gcloud run services delete --region=$region background

A seguir