Procesamiento en segundo plano

Muchas apps necesitan procesar en segundo plano fuera del contexto de una solicitud web. En este instructivo, se crea una app web que permite a los usuarios ingresar texto para traducir y, luego, muestra una lista de traducciones anteriores. La traducción se realiza en segundo plano para evitar que se bloquee la solicitud del usuario.

En el siguiente diagrama se ilustra el proceso de solicitud de traducción.

Diagrama de la arquitectura

Esta es la secuencia de eventos de cómo funciona la app del instructivo:

  1. Visita la página web para ver una lista de traducciones anteriores almacenadas en Firestore.
  2. Ingresa un formulario HTML para solicitar una traducción de texto.
  3. La solicitud de traducción se publica en Pub/Sub.
  4. Se activa un servicio de Cloud Run suscrito a ese tema Pub/Sub.
  5. El servicio de Cloud Run usa Cloud Translation para traducir el texto.
  6. El servicio de Cloud Run almacena el resultado en Firestore.

Este instructivo está dirigido a cualquier persona que desee aprender sobre el procesamiento en segundo plano con Cloud de Confiance by S3NS. No se requiere experiencia en Pub/Sub, Firestore o Cloud Run. Sin embargo, para comprender todo el código, es útil tener experiencia en Java y HTML.

Objetivos

  • Comprenda e implemente un servicio Cloud Run.
  • Probar la app

Costos

En este documento, usarás los siguientes componentes facturables de Cloud de Confiance by S3NS:

Cuando completes las tareas que se describen en este documento, podrás borrar los recursos que creaste para evitar que se te siga facturando. Para obtener más información, consulta Realiza una limpieza.

Antes de comenzar

  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.

    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

  4. Instala Google Cloud CLI.

  5. Configura gcloud CLI para usar tu identidad federada.

    Para obtener más información, consulta Accede a la gcloud CLI con tu identidad federada.

  6. Para inicializar gcloud CLI, ejecuta el siguiente comando:

    gcloud init
  7. Actualiza los componentes de gcloud:
    gcloud components update
  8. Prepara tu entorno de desarrollo.

    Vaya a la guía de configuración de Java

Prepara la app

  1. En la ventana de la terminal, clona el repositorio de la app de muestra en la máquina local:

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

    De manera opcional, puedes descargar la muestra como un archivo ZIP y extraerla.

  2. Cambia al directorio que contiene el código de muestra de procesamiento en segundo plano:

    cd getting-started-java/background

Conoce la app

La app web tiene dos componentes principales:

  • Un servidor HTTP de Java para manejar solicitudes web. El servidor tiene los siguientes dos extremos:
    • /translate
      • GET (mediante un navegador web): muestra las 10 solicitudes de traducción procesadas más recientes enviadas por los usuarios.
      • POST (con una suscripción Pub/Sub): procesa las solicitudes de traducción mediante la API de Cloud Translation y almacena los resultados en Firestore.
    • /create: El formulario para enviar solicitudes nuevas de traducción.
  • Clientes de servicios que procesan las solicitudes de traducción que se envían mediante el formulario web. Existen tres clientes que trabajan juntos:
    • Pub/Sub: Cuando un usuario envía el formulario web, el cliente de Pub/Sub publica un mensaje con los detalles de la solicitud. Una suscripción creada en este instructivo retransmite esos mensajes al extremo de Cloud Run que creas para realizar traducciones.
    • Traducción: este cliente maneja las solicitudes de Pub/Sub mediante las traducciones.
    • Firestore: Cuando se completa la traducción, el cliente almacena los datos de la solicitud junto con la traducción en Firestore. Este cliente también lee las solicitudes más recientes en el extremo /translate principal.

Comprende el código de Cloud Run

  • La app de Cloud Run depende de Firestore, Translation y 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>
  • Los clientes globales de Firestore, Translation y Pub/Sub se inicializan para que se puedan volver a usar entre invocaciones. De esa manera, no es necesario inicializar nuevos clientes para cada invocación, lo que ralentizaría la ejecución.

    @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();
          }
        }
      }
    }
  • El controlador de índices (/) accede a todas las traducciones existentes de Firestore y completa una plantilla HTML con la 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);
    }
  • Para solicitar una traducción nueva, se debe enviar un formulario HTML. El controlador de solicitudes de traducción, registrado en /create, analiza el envío del formulario, valida la solicitud y publica un mensaje en 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 suscripción Pub/Sub que creas reenvía esas solicitudes al extremo de Cloud Run, que analiza el mensaje Pub/Sub para que el texto se traduzca y el idioma de destino deseado. La API de traducción traduce la cadena al idioma que seleccionaste.

    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()));
  • La app almacena los datos de traducción en un nuevo documento que crea en 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());

Implementa la app de Cloud Run

  1. Elige un nombre de tema de Pub/Sub y genere un token de verificación de Pub/Sub mediante uuidgen o un generador de UUID en línea como uuidgenerator.net. Este token asegurará que el extremo de Cloud Run solo acepte solicitudes de la suscripción Pub/Sub que creas.

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

     gcloud pubsub topics create $PUBSUB_TOPIC
    
    • Reemplaza MY_PROJECT en el archivo pom.xml por el ID de tu proyecto Cloud de Confiance .
  3. Crea e implementa una imagen del código en GCR (un repositorio de imágenes) con el complemento Jib de Maven.

     mvn clean package jib:build
    
  4. Implementa la aplicación en 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

    donde MY_PROJECT es el nombre del proyecto deCloud de Confiance que creaste. Este comando genera el extremo al que tu suscripción Pub/Sub empuja las solicitudes de traducción. Toma nota de este extremo, ya que lo necesitarás para crear la suscripción Pub/Sub, y lo visitarás en un navegador para solicitar una nueva traducción.

Prueba la app

Después de implementar el servicio de Cloud Run, intenta solicitar una traducción.

  1. Para ver la app en el navegador, ve al extremo de Cloud Run que creaste antes.

    Hay una página con una lista vacía de traducciones y un formulario para solicitar traducciones nuevas.

  2. Haz clic en + Solicitar traducción, completa el formulario de solicitud y, luego, haz clic en Enviar.

  3. El envío te regresa de forma automática a la ruta /translate, pero la nueva traducción podría no aparecer todavía. Para actualizar la página, haz clic en Actualizar . Aparece una fila nueva en la lista de traducciones. Si no ves la traducción, espera unos segundos y vuelve a intentarlo. Si la traducción sigue sin aparecer, consulta la siguiente sección sobre cómo depurar la app.

Depura la app

Si no puedes conectarte al servicio de Cloud Run o no ves nuevas traducciones, verifica lo siguiente:

  • Comprueba que el comando gcloud run deploy se completó con éxito y que no se generó ningún error. Si hubo errores (por ejemplo, message=Build failed), corrígelos y vuelve a intentarlo.

  • Comprueba si hay errores en los registros.

    1. En la consola de Cloud de Confiance , ve a la página de Cloud Run.

      Ir a la página Cloud Run

    2. Haz clic en el nombre del servicio, background.

    3. Haga clic en Registros.

Limpia

Borra el proyecto

  1. En la Cloud de Confiance consola, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra los servicios de Cloud Run.

  • Borra los servicios de Cloud Run que creaste en este instructivo:

    gcloud run services delete --region=$region background

¿Qué sigue?

  • Obtén más información sobre Cloud Run.
  • Prueba Cloud Run, que te permite ejecutar contenedores sin estado en un entorno completamente administrado o en tu propio clúster de Google Kubernetes Engine.