Pemrosesan latar belakang dengan Go


Banyak aplikasi perlu melakukan pemrosesan latar belakang di luar konteks permintaan web. Tutorial ini membuat aplikasi web yang memungkinkan pengguna memasukkan teks untuk diterjemahkan, lalu menampilkan daftar terjemahan sebelumnya. Terjemahan dilakukan dalam proses latar belakang untuk menghindari pemblokiran permintaan pengguna.

Diagram berikut mengilustrasikan proses permintaan terjemahan.

Diagram arsitektur.

Berikut adalah urutan peristiwa untuk cara kerja aplikasi tutorial:

  1. Kunjungi halaman web untuk melihat daftar terjemahan sebelumnya, yang disimpan di Firestore.
  2. Minta terjemahan teks dengan memasukkan formulir HTML.
  3. Permintaan terjemahan dipublikasikan ke Pub/Sub.
  4. Cloud Function yang berlangganan topik Pub/Sub tersebut akan dipicu.
  5. Cloud Function menggunakan Cloud Translation untuk menerjemahkan teks.
  6. Cloud Function menyimpan hasilnya di Firestore.

Tutorial ini ditujukan bagi siapa saja yang tertarik untuk mempelajari pemrosesan latar belakang dengan Google Cloud. Anda tidak memerlukan pengalaman terkait Pub/Sub, Firestore, App Engine, atau Cloud Functions. Namun, untuk memahami semua kode, beberapa pengalaman dengan Go, JavaScript, dan HTML akan sangat membantu.

Tujuan

  • Memahami dan men-deploy Cloud Function.
  • Memahami dan men-deploy aplikasi App Engine.
  • Coba aplikasi.

Biaya

Dalam dokumen ini, Anda menggunakan komponen Google Cloud yang dapat ditagih berikut:

Untuk membuat perkiraan biaya berdasarkan proyeksi penggunaan Anda, gunakan kalkulator harga. Pengguna baru Google Cloud mungkin memenuhi syarat untuk mendapatkan uji coba gratis.

Setelah menyelesaikan tugas yang dijelaskan dalam dokumen ini, Anda dapat menghindari penagihan berkelanjutan dengan menghapus resource yang Anda buat. Untuk mengetahui informasi selengkapnya, lihat Pembersihan.

Sebelum memulai

  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. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

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

  4. Aktifkan API Firestore, Cloud Functions, Pub/Sub, and Cloud Translation.

    Mengaktifkan API

  5. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

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

  7. Aktifkan API Firestore, Cloud Functions, Pub/Sub, and Cloud Translation.

    Mengaktifkan API

  8. Di konsol Google Cloud, buka aplikasi di Cloud Shell.

    Buka Cloud Shell

    Cloud Shell menyediakan akses command line ke resource cloud langsung dari browser. Buka Cloud Shell di browser Anda dan klik Continue untuk mendownload kode contoh dan berpindah ke direktori aplikasi.

  9. Di Cloud Shell, konfigurasikan alat gcloud untuk menggunakan project Google Cloud Anda:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Memahami Cloud Function

  • Fungsi ini dimulai dengan mengimpor beberapa dependensi, seperti Firestore dan Translation. Fungsi ini juga memiliki beberapa variabel dan jenis global.
    
    // Package background contains a Cloud Function to translate text.
    // The function listens to Pub/Sub, does the translations, and stores the
    // result in Firestore.
    package background
    
    import (
    	"context"
    	"crypto/sha512"
    	"encoding/base64"
    	"encoding/json"
    	"fmt"
    	"os"
    	"strings"
    
    	"cloud.google.com/go/firestore"
    	"cloud.google.com/go/translate"
    	"golang.org/x/text/language"
    	"google.golang.org/grpc/codes"
    	"google.golang.org/grpc/status"
    )
    
    // A Translation contains the original and translated text.
    type Translation struct {
    	Original         string `json:"original"`
    	Translated       string `json:"translated"`
    	OriginalLanguage string `json:"original_language"`
    	Language         string `json:"language"`
    }
    
    // Clients reused between function invocations.
    var (
    	translateClient *translate.Client
    	firestoreClient *firestore.Client
    )
    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See https://cloud.google.com/functions/docs/calling/pubsub.
    type PubSubMessage struct {
    	Data []byte `json:"data"`
    }
    
  • Klien Firestore dan Translation global telah diinisialisasi agar dapat digunakan kembali di antara pemanggilan fungsi. Dengan demikian, Anda tidak perlu menginisialisasi klien baru untuk setiap pemanggilan fungsi, yang akan memperlambat eksekusi.
    
    // initializeClients creates translateClient and firestoreClient if they haven't
    // been created yet.
    func initializeClients() error {
    	projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
    	if projectID == "" {
    		return fmt.Errorf("GOOGLE_CLOUD_PROJECT must be set")
    	}
    
    	if translateClient == nil {
    		// Pre-declare err to avoid shadowing translateClient.
    		var err error
    		// Use context.Background() so the client can be reused.
    		translateClient, err = translate.NewClient(context.Background())
    		if err != nil {
    			return fmt.Errorf("translate.NewClient: %w", err)
    		}
    	}
    	if firestoreClient == nil {
    		// Pre-declare err to avoid shadowing firestoreClient.
    		var err error
    		// Use context.Background() so the client can be reused.
    		firestoreClient, err = firestore.NewClient(context.Background(), projectID)
    		if err != nil {
    			return fmt.Errorf("firestore.NewClient: %w", err)
    		}
    	}
    	return nil
    }
    
  • Translation API menerjemahkan string ke bahasa yang Anda pilih.
    
    // translateString translates text to lang, returning:
    // * the translated text,
    // * the automatically detected source language, and
    // * an error.
    func translateString(ctx context.Context, text string, lang string) (translated string, originalLang string, err error) {
    	l, err := language.Parse(lang)
    	if err != nil {
    		return "", "", fmt.Errorf("language.Parse: %w", err)
    	}
    
    	outs, err := translateClient.Translate(ctx, []string{text}, l, nil)
    	if err != nil {
    		return "", "", fmt.Errorf("Translate: %w", err)
    	}
    
    	if len(outs) < 1 {
    		return "", "", fmt.Errorf("Translate got %d translations, need at least 1", len(outs))
    	}
    
    	return outs[0].Text, outs[0].Source.String(), nil
    }
    
  • Cloud Function dimulai dengan menginisialisasi klien Firestore dan Pub/Sub. Selanjutnya, Bard akan mengurai pesan Pub/Sub untuk mendapatkan teks yang akan diterjemahkan dan bahasa target yang diinginkan.

    Kemudian, aplikasi akan memberikan nama unik untuk permintaan terjemahan guna memastikan permintaan terjemahan tidak menyimpan terjemahan duplikat. Kemudian, proses ini akan menerjemahkannya ke dalam transaksi Firestore untuk memastikan eksekusi serentak tidak menjalankan terjemahan yang sama dua kali secara tidak sengaja.

    
    // Translate translates the given message and stores the result in Firestore.
    func Translate(ctx context.Context, m PubSubMessage) error {
    	initializeClients()
    
    	t := Translation{}
    	if err := json.Unmarshal(m.Data, &t); err != nil {
    		return fmt.Errorf("json.Unmarshal: %w", err)
    	}
    
    	// Use a unique document name to prevent duplicate translations.
    	key := fmt.Sprintf("%s/%s", t.Language, t.Original)
    	sum := sha512.Sum512([]byte(key))
    	// Base64 encode the sum to make a nice string. The [:] converts the byte
    	// array to a byte slice.
    	docName := base64.StdEncoding.EncodeToString(sum[:])
    	// The document name cannot contain "/".
    	docName = strings.Replace(docName, "/", "-", -1)
    	ref := firestoreClient.Collection("translations").Doc(docName)
    
    	// Run in a transation to prevent concurrent duplicate translations.
    	err := firestoreClient.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
    		doc, err := tx.Get(ref)
    		if err != nil && status.Code(err) != codes.NotFound {
    			return fmt.Errorf("Get: %w", err)
    		}
    		// Do nothing if the document already exists.
    		if doc.Exists() {
    			return nil
    		}
    
    		translated, originalLang, err := translateString(ctx, t.Original, t.Language)
    		if err != nil {
    			return fmt.Errorf("translateString: %w", err)
    		}
    		t.Translated = translated
    		t.OriginalLanguage = originalLang
    
    		if err := tx.Set(ref, t); err != nil {
    			return fmt.Errorf("Set: %w", err)
    		}
    		return nil
    	})
    
    	if err != nil {
    		return fmt.Errorf("RunTransaction: %w", err)
    	}
    	return nil
    }
    

Men-deploy Cloud Function

  • Di Cloud Shell, pada direktori yang sama dengan file translate.go, deploy Cloud Function dengan pemicu Pub/Sub:

    gcloud functions deploy Translate --runtime go111 \
    --trigger-topic=translate --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_GOOGLE_CLOUD_PROJECT

    dengan YOUR_GOOGLE_CLOUD_PROJECT sebagai project ID Google Cloud Anda.

Memahami aplikasi

Ada dua komponen utama untuk aplikasi web:

  • Server HTTP Go untuk menangani permintaan web. Server memiliki dua endpoint berikut:
    • /: Mencantumkan semua terjemahan yang ada dan menampilkan formulir yang dapat dikirimkan pengguna untuk meminta terjemahan baru.
    • /request-translation: Pengiriman formulir dikirim ke endpoint ini, yang memublikasikan permintaan ke Pub/Sub untuk diterjemahkan secara asinkron.
  • Template HTML yang diisi dengan terjemahan yang sudah ada oleh server Go.

Server HTTP

  • Dalam direktori index, main.go dimulai dengan menyiapkan aplikasi dan mendaftarkan pengendali HTTP:

    
    // Command index is an HTTP app that displays all previous translations
    // (stored in Firestore) and has a form to request new translations. On form
    // submission, the request is sent to Pub/Sub to be processed in the background.
    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"html/template"
    	"log"
    	"net/http"
    	"os"
    	"path/filepath"
    
    	"cloud.google.com/go/firestore"
    	"cloud.google.com/go/pubsub"
    	"github.com/GoogleCloudPlatform/golang-samples/getting-started/background"
    )
    
    // topicName is the Pub/Sub topic to publish requests to. The Cloud Function to
    // process translation requests should be subscribed to this topic.
    const topicName = "translate"
    
    // An app holds the clients and parsed templates that can be reused between
    // requests.
    type app struct {
    	pubsubClient    *pubsub.Client
    	pubsubTopic     *pubsub.Topic
    	firestoreClient *firestore.Client
    	tmpl            *template.Template
    }
    
    func main() {
    	projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
    	if projectID == "" {
    		log.Fatalf("GOOGLE_CLOUD_PROJECT must be set")
    	}
    
    	a, err := newApp(projectID, "index")
    	if err != nil {
    		log.Fatalf("newApp: %v", err)
    	}
    
    	http.HandleFunc("/", a.index)
    	http.HandleFunc("/request-translation", a.requestTranslation)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    
    	log.Printf("Listening on localhost:%v", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // newApp creates a new app.
    func newApp(projectID, templateDir string) (*app, error) {
    	ctx := context.Background()
    
    	pubsubClient, err := pubsub.NewClient(ctx, projectID)
    	if err != nil {
    		return nil, fmt.Errorf("pubsub.NewClient: %w", err)
    	}
    
    	pubsubTopic := pubsubClient.Topic(topicName)
    
    	firestoreClient, err := firestore.NewClient(ctx, projectID)
    	if err != nil {
    		return nil, fmt.Errorf("firestore.NewClient: %w", err)
    	}
    
    	// Template referenced relative to the module/app root.
    	tmpl, err := template.ParseFiles(filepath.Join(templateDir, "index.html"))
    	if err != nil {
    		return nil, fmt.Errorf("template.New: %w", err)
    	}
    
    	return &app{
    		pubsubClient: pubsubClient,
    		pubsubTopic:  pubsubTopic,
    
    		firestoreClient: firestoreClient,
    		tmpl:            tmpl,
    	}, nil
    }
    
  • Pengendali indeks (/) mendapatkan semua terjemahan yang ada dari Firestore dan mengisi template HTML dengan daftar:

    
    // index lists the current translations.
    func (a *app) index(w http.ResponseWriter, r *http.Request) {
    	docs, err := a.firestoreClient.Collection("translations").Documents(r.Context()).GetAll()
    	if err != nil {
    		log.Printf("GetAll: %v", err)
    		http.Error(w, fmt.Sprintf("Error getting translations: %v", err), http.StatusInternalServerError)
    		return
    	}
    
    	var translations []background.Translation
    	for _, d := range docs {
    		t := background.Translation{}
    		if err := d.DataTo(&t); err != nil {
    			log.Printf("DataTo: %v", err)
    			http.Error(w, "Error reading translations", http.StatusInternalServerError)
    			return
    		}
    		translations = append(translations, t)
    	}
    
    	if err := a.tmpl.Execute(w, translations); err != nil {
    		log.Printf("tmpl.Execute: %v", err)
    		http.Error(w, "Error writing response", http.StatusInternalServerError)
    		return
    	}
    }
    
  • Terjemahan baru diminta dengan mengirimkan formulir HTML. Pengendali terjemahan permintaan, yang terdaftar di /request-translation, mengurai pengiriman formulir, memvalidasi permintaan, dan memublikasikan pesan ke Pub/Sub:

    
    // requestTranslation parses the request, validates it, and sends it to Pub/Sub.
    func (a *app) requestTranslation(w http.ResponseWriter, r *http.Request) {
    	if err := r.ParseForm(); err != nil {
    		log.Printf("ParseForm: %v", err)
    		http.Error(w, "Bad request", http.StatusBadRequest)
    		return
    	}
    	v := r.PostFormValue("v")
    	if v == "" {
    		log.Printf("Empty value")
    		http.Error(w, "Empty value", http.StatusBadRequest)
    		return
    	}
    	acceptableLanguages := map[string]bool{
    		"de": true,
    		"en": true,
    		"es": true,
    		"fr": true,
    		"ja": true,
    		"sw": true,
    	}
    	lang := r.PostFormValue("lang")
    	if !acceptableLanguages[lang] {
    		log.Printf("Unsupported language: %v", lang)
    		http.Error(w, fmt.Sprintf("Unsupported language: %v", lang), http.StatusBadRequest)
    		return
    	}
    
    	log.Printf("Translation requested: %q -> %s", v, lang)
    
    	t := background.Translation{
    		Original: v,
    		Language: lang,
    	}
    	msg, err := json.Marshal(t)
    	if err != nil {
    		log.Printf("json.Marshal: %v", err)
    		http.Error(w, "Error requesting translation", http.StatusInternalServerError)
    		return
    	}
    
    	res := a.pubsubTopic.Publish(r.Context(), &pubsub.Message{Data: msg})
    	if _, err := res.Get(r.Context()); err != nil {
    		log.Printf("Publish.Get: %v", err)
    		http.Error(w, "Error requesting translation", http.StatusInternalServerError)
    		return
    	}
    }
    

{i>Template<i} HTML

Template HTML adalah dasar untuk halaman HTML yang ditampilkan kepada pengguna sehingga mereka dapat melihat terjemahan sebelumnya dan meminta terjemahan baru. Template tersebut diisi oleh server HTTP dengan daftar terjemahan yang ada.

  • Elemen <head> dari template HTML menyertakan metadata, style sheet, dan JavaScript untuk halaman:
    <html>
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Translations</title>
    
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
        <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script>
            $(document).ready(function() {
                $("#translate-form").submit(function(e) {
                    e.preventDefault();
                    // Get value, make sure it's not empty.
                    if ($("#v").val() == "") {
                        return;
                    }
                    $.ajax({
                        type: "POST",
                        url: "/request-translation",
                        data: $(this).serialize(),
                        success: function(data) {
                            // Show snackbar.
                            console.log(data);
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--red-100");
                            $("#snackbar").addClass("mdl-color--green-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation requested'
                            });
                        },
                        error: function(data) {
                            // Show snackbar.
                            console.log("Error requesting translation");
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--green-100");
                            $("#snackbar").addClass("mdl-color--red-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation request failed'
                            });
                        }
                    });
                });
            });
        </script>
        <style>
            .lang {
                width: 50px;
            }
            .translate-form {
                display: inline;
            }
        </style>
    </head>

    Halaman ini mengambil aset CSS dan JavaScript Material Design Lite (MDL). MDL memungkinkan Anda menambahkan tampilan dan nuansa Desain Material ke situs Anda.

    Halaman ini menggunakan JQuery untuk menunggu hingga dokumen selesai dimuat dan menetapkan pengendali pengiriman formulir. Setiap kali formulir permintaan terjemahan dikirimkan, halaman akan melakukan validasi formulir minimal untuk memastikan nilainya tidak kosong, lalu mengirimkan permintaan asinkron ke endpoint /request-translation.

    Terakhir, snackbar MDL akan muncul untuk menunjukkan apakah permintaan berhasil atau jika mengalami error.

  • Isi HTML halaman menggunakan tata letak MDL dan beberapa komponen MDL untuk menampilkan daftar terjemahan dan formulir untuk meminta terjemahan tambahan:
    <body>
        <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
            <header class="mdl-layout__header">
                <div class="mdl-layout__header-row">
                    <!-- Title -->
                    <span class="mdl-layout-title">Translate with Background Processing</span>
                </div>
            </header>
            <main class="mdl-layout__content">
                <div class="page-content">
                    <div class="mdl-grid">
                    <div class="mdl-cell mdl-cell--1-col"></div>
                        <div class="mdl-cell mdl-cell--3-col">
                            <form id="translate-form" class="translate-form">
                                <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                                    <input class="mdl-textfield__input" type="text" id="v" name="v">
                                    <label class="mdl-textfield__label" for="v">Text to translate...</label>
                                </div>
                                <select class="mdl-textfield__input lang" name="lang">
                                    <option value="de">de</option>
                                    <option value="en">en</option>
                                    <option value="es">es</option>
                                    <option value="fr">fr</option>
                                    <option value="ja">ja</option>
                                    <option value="sw">sw</option>
                                </select>
                                <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent" type="submit"
                                    name="submit">Submit</button>
                            </form>
                        </div>
                        <div class="mdl-cell mdl-cell--8-col">
                            <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
                                <thead>
                                    <tr>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Original</strong></th>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Translation</strong></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {{range .}}
                                    <tr>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--primary">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ .OriginalLanguage }} </span>
                                            </span>
                                        {{ .Original }}
                                        </td>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--accent">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ .Language }} </span>
                                            </span>
                                            {{ .Translated }}
                                        </td>
                                    </tr>
                                    {{end}}
                                </tbody>
                            </table>
                            <br/>
                            <button class="mdl-button mdl-js-button mdl-button--raised" type="button" onClick="window.location.reload();">
                                Refresh
                            </button>
                        </div>
                    </div>
                </div>
                <div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar" id="snackbar">
                    <div class="mdl-snackbar__text mdl-color-text--black"></div>
                    <button type="button" class="mdl-snackbar__action"></button>
                </div>
            </main>
        </div>
    </body>
    
    </html>

Membangun aplikasi

  • Sebelum mencoba men-deploy aplikasi web, build aplikasi untuk memastikan aplikasi tersebut dikompilasi dan semua dependensi Anda berfungsi.
    go build -o start ./index
    

    Build akan berhasil jika tidak ada yang dicetak dan file start dibuat.

Men-deploy aplikasi web

Anda dapat menggunakan lingkungan standar App Engine untuk membangun dan men-deploy aplikasi yang berjalan dengan andal dalam beban yang berat dan dengan data dalam jumlah besar.

Tutorial ini menggunakan lingkungan standar App Engine untuk men-deploy frontend HTTP.

app.yaml mengonfigurasi aplikasi App Engine:

runtime: go111
main: index
  • Dari direktori yang sama dengan file app.yaml, deploy aplikasi Anda ke lingkungan standar App Engine:
    gcloud app deploy

Menguji aplikasi

Setelah Anda men-deploy aplikasi Cloud Function dan App Engine, coba minta terjemahan.

  1. Untuk melihat aplikasi di browser Anda,masukkan URL berikut:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    Ganti kode berikut:

    Ada halaman dengan daftar terjemahan kosong dan formulir untuk meminta terjemahan baru.

  2. Di kolom Text to translate, masukkan beberapa teks untuk diterjemahkan, misalnya, Hello, World.
  3. Pilih bahasa dari menu drop-down untuk menerjemahkan teks.
  4. Klik Submit.
  5. Untuk memuat ulang halaman, klik Refresh . Ada baris baru dalam daftar terjemahan. Jika Anda tidak melihat terjemahan, tunggu beberapa detik lagi dan coba lagi. Jika terjemahan masih tidak muncul, lihat bagian berikutnya tentang proses debug aplikasi.

Men-debug aplikasi

Jika Anda tidak dapat terhubung ke aplikasi App Engine atau tidak melihat terjemahan baru, periksa hal berikut:

  1. Pastikan perintah deploy gcloud berhasil diselesaikan dan tidak menghasilkan error apa pun. Jika terjadi error, perbaiki error tersebut, lalu coba deploy Cloud Function dan aplikasi App Engine lagi.
  2. Di konsol Google Cloud, buka halaman Logs Viewer.

    Buka halaman Logs Viewer
    1. Di menu drop-down Recent selected resources, klik GAE Application, lalu klik All module_id. Anda akan melihat daftar permintaan dari saat Anda mengunjungi aplikasi. Jika Anda tidak melihat daftar permintaan, konfirmasi bahwa Anda memilih All module_id dari menu drop-down. Jika Anda melihat pesan error yang dicetak ke Konsol Google Cloud, pastikan kode aplikasi Anda cocok dengan kode di bagian tentang memahami aplikasi.
    2. Di menu drop-down Recent selected resources, klik Cloud Function, lalu klik All function name. Anda akan melihat fungsi yang tercantum untuk setiap terjemahan yang diminta. Jika tidak, periksa apakah aplikasi Cloud Function dan App Engine menggunakan topik Pub/Sub yang sama:
      • Dalam file background/index/main.go, pastikan konstanta topicName adalah "translate".
      • Saat Anda men-deploy Cloud Function, pastikan untuk menyertakan flag --trigger-topic=translate.

Pembersihan

Agar tidak perlu membayar biaya pada akun Google Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus project yang berisi resource tersebut, atau simpan project dan hapus setiap resource.

Menghapus project Google Cloud

  1. Di konsol Google Cloud, buka halaman Manage resource.

    Buka Manage resource

  2. Pada daftar project, pilih project yang ingin Anda hapus, lalu klik Delete.
  3. Pada dialog, ketik project ID, lalu klik Shut down untuk menghapus project.

Menghapus instance App Engine

  1. Di konsol Google Cloud, buka halaman Versi untuk App Engine.

    Buka Versi

  2. Pilih kotak centang untuk versi aplikasi non-default yang ingin Anda hapus.
  3. Untuk menghapus versi aplikasi, klik Hapus.

Menghapus Cloud Function

  • Hapus Cloud Function yang Anda buat dalam tutorial ini:
    gcloud functions delete Translate

Langkah selanjutnya