Processamento em segundo plano com o Go


Para muitos apps, é necessário fazer o processamento em segundo plano fora do contexto de uma solicitação da Web. Este tutorial cria um app da Web que permite aos usuários inserir texto para traduzir e, em seguida, exibe uma lista de traduções anteriores. A tradução é feita em um processo em segundo plano para evitar o bloqueio da solicitação do usuário.

O diagrama a seguir ilustra o processo de solicitação da tradução.

Diagrama da arquitetura.

Aqui está a sequência de eventos sobre como o tutorial do app funciona:

  1. Acesse a página da Web para ver uma lista de traduções anteriores armazenadas no Firestore.
  2. Solicite uma tradução de texto inserindo um formulário HTML.
  3. A solicitação de tradução é publicada no Pub/Sub.
  4. Uma Função do Cloud inscrita nesse tópico do Pub/Sub é acionada.
  5. A Função do Cloud usa o Cloud Translation para traduzir o texto.
  6. A Função do Cloud armazena o resultado no Firestore.

Este tutorial se destina a qualquer pessoa interessada em aprender sobre o processamento em segundo plano com o Google Cloud. Nenhuma experiência anterior é necessária com Pub/Sub, Firestore, App Engine ou Cloud Functions. No entanto, para entender todo o código, convém ter experiência com Go, JavaScript e HTML.

Objetivos

  • Entenda e implante uma Função do Cloud.
  • Entenda e implante um aplicativo do App Engine.
  • Testar o app.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Ao concluir as tarefas descritas neste documento, é possível evitar o faturamento contínuo excluindo os recursos criados. Saiba mais em Limpeza.

Antes de começar

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  4. Ative as APIs Firestore, Cloud Functions, Pub/Sub, and Cloud Translation.

    Ative as APIs

  5. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  6. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  7. Ative as APIs Firestore, Cloud Functions, Pub/Sub, and Cloud Translation.

    Ative as APIs

  8. No console do Google Cloud, abra o app no Cloud Shell.

    Acessar o Cloud Shell

    O Cloud Shell oferece acesso por linha de comando aos seus recursos de nuvem diretamente no navegador. Abra o Cloud Shell no navegador e clique em Continuar para fazer o download do código de amostra e carregá-lo no diretório de app.

  9. No Cloud Shell, configure a ferramenta gcloud para usar seu projeto do Google Cloud:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Noções básicas sobre a Função do Cloud

  • A função inicia importando várias dependências, como Firestore e Translation. Ela também tem algumas variáveis e tipos globais.
    
    // 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"`
    }
    
  • Os clientes globais do Firestore e do Translation são inicializados para que possam ser reutilizados entre invocações de função. Dessa forma, não é necessário inicializar novos clientes para cada invocação de função, o que desacelera a execução.
    
    // 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
    }
    
  • A API Translation traduz a string para o idioma selecionado.
    
    // 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
    }
    
  • A Função do Cloud começa com a inicialização dos clientes do Firestore e do Pub/Sub. Em seguida, ela analisa a mensagem do Pub/Sub para receber o texto a ser traduzido e o idioma de destino escolhido.

    Em seguida, o app cria um nome único para a solicitação de tradução para garantir que ela não armazene nenhuma tradução duplicada. Depois, ele é traduzido em uma transação do Firestore para garantir que execuções simultâneas não executem acidentalmente a mesma tradução duas vezes.

    
    // 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
    }
    

Como implantar o Cloud Function

  • No Cloud Shell, no mesmo diretório do arquivo translate.go, implante a Função do Cloud com um gatilho do Pub/Sub:

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

    em que YOUR_GOOGLE_CLOUD_PROJECT é o ID do projeto no Google Cloud.

Noções básicas sobre o app

Há dois componentes principais para o app da Web:

  • Um servidor HTTP Go para lidar com pedidos da Web. O servidor tem os dois endpoints a seguir:
    • /: lista todas as traduções existentes e mostra um formulário que os usuários podem enviar para solicitar novas traduções.
    • /request-translation: os envios de formulário são enviados para esse endpoint, que publica a solicitação para o Pub/Sub ser traduzido de forma assíncrona.
  • Um modelo HTML que é preenchido com as traduções existentes pelo servidor Go.

O servidor HTTP

  • No index diretório, main.go começar configurando o app e registrar gerenciadores 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
    }
    
  • O gerenciador de índice (/) obtém todas as traduções existentes do Firestore e preenche um modelo de HTML com a lista:

    
    // 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
    	}
    }
    
  • Novas traduções são solicitadas através do envio de um formulário HTML. O gerenciador de tradução de solicitação, registrado em /request-translation, analisa o envio do formulário, valida a solicitação e publica uma mensagem no 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
    	}
    }
    

O modelo HTML

O modelo HTML é a base da página HTML exibida ao usuário para que ele possa ver as traduções anteriores e solicitar novas. O modelo é preenchido pelo servidor HTTP com a lista de traduções existentes.

  • O elemento <head> do modelo HTML inclui metadados, folhas de estilo e JavaScript para a página:
    <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>

    A página extrai recursos Material Design Lite (MDL) CSS e JavaScript. A MDL permite que você adicione uma aparência Material Design aos seus sites.

    A página usa JQuery para aguardar o carregamento do documento e definir um gerenciador de envio de formulário. Sempre que o formulário de solicitação de tradução é enviado, a página faz uma validação de formulário mínima para verificar se o valor não está vazio e envia uma solicitação assíncrona ao endpoint /request-translation.

    Por fim, uma snackbar MDL aparece para indicar se a solicitação foi bem-sucedida ou se encontrou um erro.

  • O corpo HTML da página usa um layout de MDL e vários componentes de MDL para exibir uma lista de traduções e um formulário para solicitar outras traduções:
    <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>

Como criar o app

  • Antes de tentar implantar o app da Web, crie o app para garantir que ele seja compilado e todas as dependências funcionem.
    go build -o start ./index
    

    A compilação foi bem-sucedida se nada foi impresso e um arquivo start foi criado.

Como implantar o app da Web

Use o ambiente padrão do App Engine para criar e implantar um aplicativo que será executado de maneira confiável sob grande carga e com grandes quantidades de dados.

Este tutorial usa o ambiente padrão do App Engine para implantar o front-end HTTP.

O app.yaml configura o aplicativo do App Engine:

runtime: go111
main: index
  • No mesmo diretório do arquivo app.yaml, implante o aplicativo no ambiente padrão do App Engine:
    gcloud app deploy

Como testar o app

Depois de implantar o aplicativo do função do Cloud e do App Engine, tente solicitar uma tradução.

  1. Para abrir o app no navegador,digite este URL:

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

    Substitua:

    Há uma página com uma lista vazia de traduções e um formulário para solicitar novas traduções.

  2. No campo Text to translate, digite algum texto para traduzir, por exemplo, Hello, World.
  3. Na lista suspensa, selecione um idioma para o qual você quer traduzir o texto.
  4. Clique em Enviar.
  5. Para atualizar a página, clique em Atualizar . Há uma nova linha na lista de tradução. Se você não ver uma tradução, aguarde mais alguns segundos e tente novamente. Caso você ainda não veja uma tradução, consulte a próxima seção sobre como depurar o app.

Como depurar o aplicativo

Se você não conseguir se conectar ao aplicativo do App Engine ou não vir novas traduções, verifique o seguinte:

  1. Verifique se os comandos de implantação gcloud foram concluídos com êxito e não geraram erros. Se houver erros, corrija-os e tente implantar o Cloud Function e o aplicativo do App Engine novamente.
  2. No console do Google Cloud, acesse a página do visualizador de registros.

    Acessar a página "Visualizador de registros"
    1. Na lista suspensa Recursos selecionados recentemente, clique em Aplicação do GAE e, em seguida, clique em Todos os module_id. Você verá uma lista de solicitações de quando visitou seu app. Caso contrário, verifique se você selecionou Todos os module_id na lista suspensa. Se você vir mensagens de erro impressas no console do Google Cloud, verifique se o código do seu aplicativo corresponde ao código na seção sobre como entender o aplicativo.
    2. Na lista suspensa Recursos selecionados recentemente, clique em Função do Cloud e, em seguida, clique em Todos os nomes de função. Você verá uma função listada para cada tradução solicitada. Caso contrário, verifique se o app da Função do Cloud e do App Engine está usando o mesmo tópico do Pub/Sub:
      • No arquivo background/index/main.go, verifique se a constante topicName é "translate".
      • Quando você implantar a Função do Cloud, não se esqueça de incluir a sinalização --trigger-topic=translate.

Limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados no tutorial, exclua o projeto que os contém ou mantenha o projeto e exclua os recursos individuais.

Excluir o projeto do Google Cloud

  1. No Console do Google Cloud, acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Excluir a instância do App Engine

  1. No Console do Google Cloud, acesse a página Versões do App Engine.

    Acessar "Versões"

  2. Marque a caixa de seleção da versão não padrão do app que você quer excluir.
  3. Para excluir a versão do app, clique em Excluir.

Excluir a Função do Cloud

  • Exclua a função do Cloud que você criou neste tutorial:
    gcloud functions delete Translate

A seguir