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 la procedura di richiesta di traduzione.

Diagramma dell'architettura.

Di seguito è riportata la sequenza di eventi che descrive il funzionamento dell'app tutorial:

  1. Visita la pagina web per visualizzare un elenco delle traduzioni precedenti, archiviate in Firestore.
  2. Richiedi la traduzione di un testo inserendo un modulo HTML.
  3. La richiesta di traduzione viene pubblicata su Pub/Sub.
  4. Viene attivato un servizio Cloud Run iscritto 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 di più sull'elaborazione in background con Cloud de Confiance by S3NS. Non è richiesta alcuna esperienza pregressa con Pub/Sub, Firestore e 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.
  • Prova l'app.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Cloud de Confiance by S3NS:

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. In the Cloud de Confiance console, on the project selector page, select or create a Cloud de Confiance 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

  2. Verify that billing is enabled for your Cloud de Confiance project.

  3. Enable the Firestore, Pub/Sub, and Cloud Translation APIs, if any are not already enabled.

    Roles required to enable APIs

    To enable APIs, you need the serviceusage.services.enable permission. If you created the project, then you likely already have this permission through the Owner role (roles/owner). Otherwise, you can get this permission through the Service Usage Admin role (roles/serviceusage.serviceUsageAdmin). Learn how to grant roles.

    Enable the APIs

  4. Install the Google Cloud CLI.

  5. Configure the gcloud CLI to use your federated identity.

    For more information, see Sign in to the gcloud CLI with your federated identity.

  6. To initialize the gcloud CLI, run the following command:

    gcloud init
  7. Aggiorna i componenti di gcloud:
    gcloud components update
  8. Prepara l'ambiente di sviluppo.

    Vai alla guida alla configurazione di Java

Preparazione dell'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 di elaborazione in background:

    cd getting-started-java/background

Informazioni sull'app

L'app web è composta da due componenti principali:

  • Un server HTTP Java per gestire le richieste web. Il server ha i seguenti due endpoint:
    • /translate
      • GET (utilizzando un browser web): mostra 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 servizio che elaborano le richieste di traduzione inviate tramite il modulo web. Esistono tre client che funzionano 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 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 memorizza 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 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 da poter essere riutilizzati tra le invocazioni. 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 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 Cloud Run, che analizza il messaggio Pub/Sub per ottenere 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());

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 progetto Cloud de Confiance .
  3. Crea e implementa un'immagine del tuo codice in GCR (un repository di immagini) con il plug-in Maven Jib.

     mvn clean package jib:build
    
  4. Fai 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 progettoCloud de Confiance che hai creato. Questo output comando restituisce l'endpoint a cui l'abbonamento Pub/Sub invia le 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 Cloud Run che hai creato in precedenza.

    Viene visualizzata 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, quindi 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 altro secondo e riprova. Se continui a non vedere una traduzione, consulta la sezione successiva sul debug dell'app.

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 sono stati rilevati errori (ad esempio, message=Build failed), correggili e riprova a eseguire il comando.

  • Controlla la presenza di errori nei log:

    1. Nella console Cloud de Confiance , 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. Nella console Cloud de Confiance , 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, quindi 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