Background processing


Many apps need to do background processing outside of the context of a web request. This tutorial creates a web app that lets users input text to translate, and then displays a list of previous translations. The translation is done in a background process to avoid blocking the user's request.

The following diagram illustrates the translation request process.

Diagram of architecture.

Here is the sequence of events for how the tutorial app works:

  1. Visit the web page to see a list of previous translations, stored in Firestore.
  2. Request a translation of text by entering an HTML form.
  3. The translation request is published to Pub/Sub.
  4. A Cloud Run service subscribed to that Pub/Sub topic is triggered.
  5. The Cloud Run service uses Cloud Translation to translate the text.
  6. The Cloud Run service stores the result in Firestore.

This tutorial is intended for anyone who is interested in learning about background processing with Google Cloud. No prior experience is required with Pub/Sub, Firestore, Cloud Run. However, to understand all of the code, some experience with Java and HTML is helpful.

Objectives

  • Understand and deploy a Cloud Run service.
  • Try the app.

Costs

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator .

New Google Cloud users might be eligible for a free trial .

When you finish the tasks that are described in this document, you can avoid continued billing by deleting the resources that you created. For more information, see Clean up .

Before you begin

  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. Verify 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. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity .

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

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

    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.

    Enable the APIs

  11. Install the Google Cloud CLI.

  12. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity .

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

    gcloud  
    init
  14. Update gcloud components:
    gcloud  
    components  
    update
  15. Prepare your development environment.

    Go to the Java setup guide

Preparing the app

  1. In your terminal window, clone the sample app repository to your local machine:

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

    Alternatively, you can download the sample as a zip file and extract it.

  2. Change to the directory that contains the background processing sample code:

     cd 
      
    getting-started-java/background

Understanding the app

There are two main components for the web app:

  • A Java HTTP server to handle web requests. The server has the following two endpoints:
    • /translate
      • GET (by using a web browser): Displays 10 most recent processed translation requests submitted by users.
      • POST (with a Pub/Sub subscription): Processes translation requests using the Cloud Translation API and stores the results in Firestore.
    • /create : The form to submit new translate requests.
  • Service clients that process the translation requests submitted by the web form. There are three clients that work together:
    • Pub/Sub: When the web form is submitted by a user, the Pub/Sub client publishes a message with the request details. A subscription created in this tutorial relays those messages to the Cloud Run endpoint that you create to perform translations.
    • Translation: This client handles Pub/Sub requests by performing the translations.
    • Firestore: When the translation is complete, this client stores the request data along with the translation in Firestore. This client also reads the most recent requests on the main /translate endpoint.

Understanding the Cloud Run code

  • The Cloud Run app has dependencies on Firestore, Translation, and 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> 
    
  • The global Firestore, Translation, and Pub/Sub clients are initialized so they can be reused between invocations. That way, you don't have to initialize new clients for every invocation, which would slow down execution.

      @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 
     (); 
      
     } 
      
     } 
      
     } 
     } 
     
    
  • The index handler ( / ) gets all existing translations from Firestore and fills an HTML template with the list:

      @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 
     ); 
     } 
     
    
  • New translations are requested by submitting an HTML form. The request translation handler, registered at /create , parses the form submission, validates the request, and publishes a message to 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 
     ( 
     "/" 
     ); 
     } 
     
    
  • The Pub/Sub subscription that you create forwards those requests to the Cloud Run endpoint, which parses the Pub/Sub message to get the text to translate and the desired target language. The Translation API then translates the string to the language you selected.

      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 
     ())); 
     
    
  • The app stores the translation data in a new document it creates 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 
     ()); 
     
    

Deploying the Cloud Run app

  1. Choose a Pub/Sub Topic Name and generate a Pub/Sub Verification Token using uuidgen or an online UUID generator such as uuidgenerator.net . This token will ensure that the Cloud Run endpoint only accepts requests from the Pub/Sub subscription you create.

     export 
      
     PUBSUB_TOPIC 
     = 
    background-translate export 
      
     PUBSUB_VERIFICATION_TOKEN 
     = 
     your-verification-token 
    
  2. Create a Pub/Sub topic:

       
     gcloud 
      
     pubsub 
      
     topics 
      
     create 
      
     $PUBSUB_TOPIC 
     
    
    • Replace MY_PROJECT in the pom.xml file with your Google Cloud project ID.
  3. Build and deploy an image of your code to GCR (an image repository) with the Jib Maven plugin.

       
     mvn 
      
     clean 
      
     package 
      
     jib 
     : 
     build 
     
    
  4. Deploy the app to 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 
    

    Where MY_PROJECT is the name of the Google Cloud project you created. This command outputs the endpoint to which your Pub/Sub subscription pushes translation requests. Make note of this endpoint, because you will need it to create the Pub/Sub subscription, and you will visit the endpoint in a browser to request a new translation.

Testing the app

After you've deployed the Cloud Run service, try requesting a translation.

  1. To view the app in your browser, go to the Cloud Run endpoint that you previously created.

    There is a page with an empty list of translations and a form to request new translations.

  2. Click + Request Translation, fill out the request form, and then click Submit.

  3. Submission automatically brings you back to the /translate path, but the new translation might not appear yet. To refresh the page, click Refresh . There is a new row in the translation list. If you don't see a translation, wait a few more seconds and try again. If you still don't see a translation, see the next section about debugging the app.

Debugging the app

If you cannot connect to your Cloud Run service or don't see new translations, check the following:

  • Check that the gcloud run deploy command successfully completed and didn't output any errors. If there were errors (for example, message=Build failed ), fix them, and try running again.

  • Check for errors in the logs:

    1. In the Google Cloud console, go to the Cloud Run page.

      Go to Cloud Run page

    2. Click the service name, background .

    3. Click Logs.

Clean up

Delete the project

  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.

Delete the Cloud Run services.

  • Delete the Cloud Run services you created in this tutorial:

    gcloud  
    run  
    services  
    delete  
    --region = 
     $region 
      
    background

What's next

Create a Mobile Website
View Site in Mobile | Classic
Share by: