Elaborazione in background

Molte app devono eseguire l'elaborazione in background al di fuori del contesto di una richiesta web. Questo tutorial crea un'app web che consente agli utenti di inserire testo per tradurre, quindi mostra un elenco di traduzioni precedenti. La traduzione viene eseguita in background per evitare di bloccare la richiesta dell'utente.

Il seguente diagramma illustra il processo di richiesta di traduzione.

Diagramma dell'architettura.

Ecco la sequenza degli eventi per come funziona l'app del tutorial:

  1. Visita la pagina web per visualizzare un elenco di traduzioni precedenti, archiviate inFistore.
  2. Richiedi una traduzione del testo inserendo un modulo HTML.
  3. La richiesta di traduzione viene pubblicata in Pub/Sub.
  4. Viene attivato un servizio Cloud Run sottoscritto a tale argomento Pub/Sub.
  5. Il servizio Cloud Run utilizza Cloud Translation per tradurre il testo.
  6. Il servizio Cloud Run archivia il risultato in Firestore.

Questo tutorial è rivolto a chiunque sia interessato a conoscere l'elaborazione in background con Google Cloud. Non sono richieste esperienze precedenti con Pub/Sub, Firestore e Cloud Run. Tuttavia, per comprendere tutto il codice, è utile avere esperienza con Java e HTML.

Obiettivi

  • Capire ed eseguire il deployment di un servizio Cloud Run.
  • Prova l'app.

Costi

In questo documento utilizzi i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi basata sull'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud potrebbero essere idonei per una prova gratuita.

Una volta completate le attività descritte in questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, consulta la pagina Pulizia.

Prima di iniziare

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Firestore, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  5. Install the Google Cloud CLI.
  6. To initialize the gcloud CLI, run the following command:

    gcloud init
  7. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  8. Make sure that billing is enabled for your Google Cloud project.

  9. Enable the Firestore, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  10. Install the Google Cloud CLI.
  11. To initialize the gcloud CLI, run the following command:

    gcloud init
  12. Aggiorna i componenti gcloud:
    gcloud components update
  13. Prepara l'ambiente di sviluppo.

    Vai alla guida alla configurazione di Java

Preparazione dell'app in corso...

  1. Nella finestra del terminale, clona il repository dell'app di esempio nella tua macchina locale:

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

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

  2. Passa alla directory contenente il codice di esempio per l'elaborazione in background:

    cd getting-started-java/background

Comprendere l'app

Esistono due componenti principali per l'app web:

  • Un server HTTP Java per gestire le richieste web. Il server ha i seguenti due endpoint:
    • /translate
      • GET (utilizzando un browser web): visualizza le 10 richieste di traduzione elaborate più di recente inviate dagli utenti.
      • POST (con un abbonamento Pub/Sub): elabora le richieste di traduzione utilizzando l'API Cloud Translation e archivia i risultati in Firestore.
    • /create: il modulo per inviare nuove richieste di traduzione.
  • Client di servizio che elaborano le richieste di traduzione inviate dal modulo web. Ci sono tre clienti che lavorano insieme:
    • Pub/Sub: quando il modulo web viene inviato da un utente, il client Pub/Sub pubblica un messaggio con i dettagli della richiesta. Una sottoscrizione creata in questo tutorial inoltra i messaggi all'endpoint di Cloud Run che hai creato per eseguire le traduzioni.
    • Traduzione: questo client gestisce le richieste Pub/Sub eseguendo le traduzioni.
    • Firestore: quando la traduzione è stata completata, questo client archivia i dati della richiesta insieme alla traduzione in Firestore. Questo client legge anche le richieste più recenti sull'endpoint /translate principale.

Informazioni sul codice Cloud Run

  • L'app Cloud Run dipende dalle librerie, 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>
  • I client globali di Firestore, Translation e Pub/Sub vengono inizializzati, quindi possono essere riutilizzati tra le chiamate. In questo modo, non dovrai inizializzare nuovi client per ogni chiamata, rallentando l'esecuzione.

    @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();
          }
        }
      }
    }
  • Il gestore di indici (/) recupera tutte le traduzioni esistenti daFistore e inserisce un modello HTML con l'elenco:

    @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);
    }
  • Per le nuove traduzioni viene inviato un modulo HTML. Il gestore di traduzione delle richieste, registrato all'indirizzo /create, analizza l'invio del modulo, convalida la richiesta e pubblica un messaggio in 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("/");
    }
  • La sottoscrizione Pub/Sub che crei inoltra queste richieste all'endpoint di Cloud Run, che analizza il messaggio Pub/Sub per ottenere la traduzione del testo e la lingua di destinazione desiderata. L'API Translation traduce la stringa nella lingua selezionata.

    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()));
  • L'app archivia i dati di traduzione in un nuovo documento che crea inFirestore.

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

Deployment dell'app Cloud Run

  1. Scegli un nome per l'argomento Pub/Sub e genera un token di verifica Pub/Sub utilizzando uuidgen oppure un generatore UUID online come uuidgenerator.net. Questo token garantirà che l'endpoint Cloud Run accetti solo le richieste dall'abbonamento Pub/Sub che crei.

    export PUBSUB_TOPIC=background-translate
    export PUBSUB_VERIFICATION_TOKEN=your-verification-token
  2. Crea un argomento Pub/Sub:

     gcloud pubsub topics create $PUBSUB_TOPIC
    
    • Sostituisci MY_PROJECT nel file pom.xml con l'ID progetto Cloud.
  3. Crea e sottoponi a deployment un'immagine del tuo codice in GCR (un repository di immagini) con il plug-in Jib Maven.

     mvn clean package jib:build
    
  4. Esegui il deployment dell'app su 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

    Dove MY_PROJECT è il nome del progetto cloud che hai creato. Questo comando genera l'endpoint in cui l'abbonamento Pub/Sub esegue il push delle richieste di traduzione. Prendi nota di questo endpoint, perché ti servirà per creare l'abbonamento Pub/Sub e lo visiterai in un browser per richiedere una nuova traduzione.

Test dell'app

Dopo aver eseguito il deployment del servizio Cloud Run, prova a richiedere una traduzione.

  1. Per visualizzare l'app nel browser, vai all'endpoint di Cloud Run che hai creato in precedenza.

    C'è una pagina con un elenco di traduzioni vuoto e un modulo per richiedere nuove traduzioni.

  2. Fai clic su + Richiedi traduzione, compila il modulo di richiesta e fai clic su Invia.

  3. Il compito ti torna automaticamente al percorso /translate, ma la nuova traduzione potrebbe non essere ancora visualizzata. Per aggiornare la pagina, fai clic su Aggiorna . C'è una nuova riga nell'elenco di traduzioni. Se non vedi una traduzione, attendi qualche secondo e riprova. Se non riesci ancora a visualizzare una traduzione, consulta la sezione successiva sul debug dell'app.

Esecuzione del debug dell'app

Se non riesci a connetterti al servizio Cloud Run o a non visualizzare nuove traduzioni, verifica quanto segue:

  • Controlla che il comando gcloud run deploy sia stato completato correttamente e che non abbia restituito errori. Se si sono verificati errori (ad esempio, message=Build failed), correggili e riprova a eseguire l'esecuzione.

  • Verifica la presenza di errori nei log:

    1. In Google Cloud Console, vai alla pagina Cloud Run.

      Vai alla pagina Cloud Run

    2. Fai clic sul nome del servizio background.

    3. Fai clic su Log.

Esegui la pulizia

Elimina il progetto

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Elimina i servizi Cloud Run.

  • Elimina i servizi Cloud Run creati in questo tutorial:

    gcloud run services delete --region=$region background

Passaggi successivi