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 il testo da tradurre e poi visualizza un elenco delle traduzioni precedenti. La traduzione viene eseguita in un processo in background per evitare di bloccare la richiesta dell'utente.

Il seguente diagramma illustra il processo di richiesta di traduzione.

Diagramma dell'architettura.

Di seguito è riportata la sequenza di eventi per il funzionamento dell'app del tutorial:

  1. Visita la pagina web per visualizzare un elenco delle traduzioni precedenti, archiviate in Firestore.
  2. Richiedi la traduzione del testo inserendo un modulo HTML.
  3. La richiesta di traduzione viene pubblicata su Pub/Sub.
  4. Viene attivato un servizio Cloud Run sottoscritto a quell'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 scoprire l'elaborazione in background con Google Cloud. Non è richiesta alcuna esperienza pregressa con Pub/Sub, Firestore, Cloud Run. Tuttavia, per comprendere tutto il codice, è utile avere una certa esperienza con Java e HTML.

Obiettivi

  • Comprendere ed eseguire il deployment di un servizio Cloud Run.
  • Provare l'app.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il calcolatore prezzi.

I nuovi Google Cloud utenti potrebbero avere diritto a una prova senza costi.

Al termine delle attività descritte in questo documento, puoi evitare l'addebito di ulteriori costi eliminando le risorse che hai creato. Per saperne di più, consulta Esegui la pulizia.

Prima di iniziare

  1. Accedi al tuo Google Cloud account. Se non conosci Google Cloud, crea un account per valutare le prestazioni dei nostri prodotti in scenari reali. I nuovi clienti ricevono anche 300 $di crediti senza costi per l'esecuzione, il test e il deployment dei workload.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

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

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. Installa Google Cloud CLI.

  6. Se utilizzi un provider di identità (IdP) esterno, devi prima accedere a gcloud CLI con la tua identità federata.

  7. Per inizializzare gcloud CLI, esegui questo comando:

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

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  9. Verify that billing is enabled for your Google Cloud project.

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

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  11. Installa Google Cloud CLI.

  12. Se utilizzi un provider di identità (IdP) esterno, devi prima accedere a gcloud CLI con la tua identità federata.

  13. Per inizializzare gcloud CLI, esegui questo comando:

    gcloud init
  14. Aggiorna i componenti gcloud:
    gcloud components update
  15. Prepara l'ambiente di sviluppo.

    Vai alla guida alla configurazione di Java

Preparare l'app

  1. Nella finestra del terminale, clona il repository dell'app di esempio sulla 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 che contiene il codice campione 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ù recenti inviate dagli utenti.
      • POST (con una sottoscrizione 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 servizi che elaborano le richieste di traduzione inviate dal modulo web. Esistono tre client che funzionano insieme:
    • Pub/Sub: quando un utente invia il modulo web, il client Pub/Sub pubblica un messaggio con i dettagli della richiesta. Una sottoscrizione creata in questo tutorial inoltra questi messaggi all'endpoint Cloud Run che crei per eseguire le traduzioni.
    • Traduzione: questo client gestisce le richieste Pub/Sub eseguendo le traduzioni.
    • Firestore: al termine della traduzione, questo client archivia i dati della richiesta insieme alla traduzione in Firestore. Questo client legge anche le richieste più recenti sull'endpoint /translate principale.

Comprendere il codice Cloud Run

  • L'app Cloud Run ha dipendenze da Firestore, Translation e Pub/Sub.

    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-firestore</artifactId>
      <version>3.13.2</version>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-translate</artifactId>
      <version>2.20.0</version>
    </dependency>
    
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-pubsub</artifactId>
      <version>1.123.17</version>
    </dependency>
  • I client globali Firestore, Translation e Pub/Sub vengono inizializzati in modo che possano essere riutilizzati tra le chiamate. In questo modo, non devi inizializzare nuovi client per ogni chiamata, il che rallenterebbe 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 dell'indice (/) recupera tutte le traduzioni esistenti da Firestore e compila 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);
    }
  • Le nuove traduzioni vengono richieste inviando un modulo HTML. Il gestore della traduzione delle richieste, registrato in /create, analizza l'invio del modulo, convalida la richiesta e pubblica un messaggio su 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 Cloud Run, che analizza il messaggio Pub/Sub per recuperare il testo da tradurre e la lingua di destinazione desiderata. L'API Translation traduce quindi 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 creato in 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());

Eseguire il deployment dell'app Cloud Run

  1. Scegli un nome argomento Pub/Sub e genera un token di verifica Pub/Sub utilizzando uuidgen o un generatore UUID online come uuidgenerator.net. Questo token garantirà che l'endpoint Cloud Run accetti solo le richieste dalla sottoscrizione 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 Google Cloud progetto.
  3. Crea ed esegui il deployment di un'immagine del codice in GCR (un repository di immagini) con il plug-in Jib Maven.

     mvn clean package jib:build
    
  4. Esegui il deployment dell'app in 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 Google Cloud progetto che hai creato. Questo comando restituisce l'endpoint a cui la sottoscrizione Pub/Sub invia le richieste di traduzione. Prendi nota di questo endpoint, perché ti servirà per creare la sottoscrizione Pub/Sub e lo visiterai in un browser per richiedere una nuova traduzione.

Testare l'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 Cloud Run che hai creato in precedenza.

    È presente una pagina con un elenco vuoto di traduzioni e un modulo per richiedere nuove traduzioni.

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

  3. L'invio ti riporta automaticamente al percorso /translate, ma la nuova traduzione potrebbe non essere ancora visualizzata. Per aggiornare la pagina, fai clic su Aggiorna . Nell'elenco delle traduzioni è presente una nuova riga. Se non vedi una traduzione, attendi qualche secondo e riprova. Se ancora non vedi una traduzione, consulta la sezione successiva relativa al debug dell'app.

Eseguire il debug dell'app

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

  • Verifica che il comando gcloud run deploy sia stato completato correttamente e non abbia generato errori. Se si sono verificati errori (ad esempio, message=Build failed), correggili e prova a eseguire di nuovo il comando.

  • Controlla la presenza di errori nei log:

    1. Nella 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.

Libera spazio

Elimina il progetto

  1. Nella Google Cloud console, vai alla pagina Gestisci risorse.

    Vai a Gestisci risorse

  2. Nell'elenco dei progetti, seleziona il progetto che vuoi eliminare, quindi fai clic su Elimina.
  3. Nella finestra di dialogo, digita l'ID progetto e fai clic su Chiudi per eliminare il progetto.

Elimina i servizi Cloud Run.

  • Elimina i servizi Cloud Run che hai creato in questo tutorial:

    gcloud run services delete --region=$region background

Passaggi successivi