Como escrever Cloud Functions

As funções do Cloud podem ser escritas nas linguagens de programação Node.js, Python, Go, Java, .NET, Ruby e PHP e ser executadas em ambientes de execução específicos de uma linguagem. O ambiente de execução do Cloud Functions varia de acordo com a linguagem de programação escolhida. As páginas de visão geral do ambiente de execução fornecem mais detalhes sobre cada ambiente de execução:

Faça um teste

Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho do Cloud Functions em situações reais. Clientes novos também ganham US$ 300 em créditos para executar, testar e implantar cargas de trabalho.

Faça uma avaliação gratuita do Cloud Functions

Tipos de Cloud Functions

Há dois tipos distintos de Cloud Functions: funções HTTP e funções baseadas em eventos. As funções baseadas em eventos podem ser funções em segundo plano ou funções do CloudEvent, dependendo do ambiente de execução do Cloud Functions para o qual foram escritas.

Funções HTTP

As funções HTTP são invocadas de solicitações HTTP padrão. Essas solicitações HTTP aguardam a resposta e são compatíveis com o processamento de métodos de solicitação HTTP comuns, como GET, PUT, POST, DELETE e OPTIONS. Quando você usa o Cloud Functions, um certificado TLS é provisionado automaticamente para você, portanto, todas as funções HTTP podem ser invocadas por meio de uma conexão segura.

Para detalhes, consulte Como escrever Funções HTTP.

Exemplo:

Node.js

const escapeHtml = require('escape-html');

/**
 * HTTP Cloud Function.
 *
 * @param {Object} req Cloud Function request context.
 *                     More info: https://expressjs.com/en/api.html#req
 * @param {Object} res Cloud Function response context.
 *                     More info: https://expressjs.com/en/api.html#res
 */
exports.helloHttp = (req, res) => {
  res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
};

Python

from flask import escape

def hello_http(request):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:
        name = request_json['name']
    elif request_args and 'name' in request_args:
        name = request_args['name']
    else:
        name = 'World'
    return 'Hello {}!'.format(escape(name))

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"encoding/json"
	"fmt"
	"html"
	"net/http"
)

// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
	var d struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	if d.Name == "" {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;

public class HelloHttp implements HttpFunction {
  private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());

  private static final Gson gson = new Gson();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Check URL parameters for "name" field
    // "world" is the default value
    String name = request.getFirstQueryParameter("name").orElse("world");

    // Parse JSON request and check for "name" field
    try {
      JsonElement requestParsed = gson.fromJson(request.getReader(), JsonElement.class);
      JsonObject requestJson = null;

      if (requestParsed != null && requestParsed.isJsonObject()) {
        requestJson = requestParsed.getAsJsonObject();
      }

      if (requestJson != null && requestJson.has("name")) {
        name = requestJson.get("name").getAsString();
      }
    } catch (JsonParseException e) {
      logger.severe("Error parsing JSON: " + e.getMessage());
    }

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Hello %s!", name);
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace HelloHttp
{
    public class Function : IHttpFunction
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public async Task HandleAsync(HttpContext context)
        {
            HttpRequest request = context.Request;
            // Check URL parameters for "name" field
            // "world" is the default value
            string name = ((string) request.Query["name"]) ?? "world";

            // If there's a body, parse it as JSON and check for "name" field.
            using TextReader reader = new StreamReader(request.Body);
            string text = await reader.ReadToEndAsync();
            if (text.Length > 0)
            {
                try
                {
                    JsonElement json = JsonSerializer.Deserialize<JsonElement>(text);
                    if (json.TryGetProperty("name", out JsonElement nameElement) &&
                        nameElement.ValueKind == JsonValueKind.String)
                    {
                        name = nameElement.GetString();
                    }
                }
                catch (JsonException parseException)
                {
                    _logger.LogError(parseException, "Error parsing JSON request");
                }
            }

            await context.Response.WriteAsync($"Hello {name}!");
        }
    }
}

Ruby

require "functions_framework"
require "cgi"
require "json"

FunctionsFramework.http "hello_http" do |request|
  # The request parameter is a Rack::Request object.
  # See https://www.rubydoc.info/gems/rack/Rack/Request
  name = request.params["name"] ||
         (JSON.parse(request.body.read)["name"] rescue nil) ||
         "World"
  # Return the response body as a string.
  # You can also return a Rack::Response object, a Rack response array, or
  # a hash which will be JSON-encoded into a response.
  "Hello #{CGI.escape_html name}!"
end

PHP


use Psr\Http\Message\ServerRequestInterface;

function helloHttp(ServerRequestInterface $request): string
{
    $name = 'World';
    $body = $request->getBody()->getContents();
    if (!empty($body)) {
        $json = json_decode($body, true);
        if (json_last_error() != JSON_ERROR_NONE) {
            throw new RuntimeException(sprintf(
                'Could not parse body: %s',
                json_last_error_msg()
            ));
        }
        $name = $json['name'] ?? $name;
    }
    $queryString = $request->getQueryParams();
    $name = $queryString['name'] ?? $name;

    return sprintf('Hello, %s!', htmlspecialchars($name));
}

Funções baseadas em eventos

O Cloud Functions usa funções baseadas em eventos para processar eventos da infraestrutura do Cloud, como mensagens em um tópico do Pub/Sub ou alterações em um bucket do Cloud Storage.

O Cloud Functions é compatível com dois subtipos de funções baseadas em eventos:

Como explicado abaixo, o subtipo usado será regido pelo ambiente de execução que sua função segmenta.

Funções em segundo plano

Funções baseadas em eventos escritas para os ambientes de execução Node.js, Python, Go e Java do Cloud Functions são conhecidas como funções em segundo plano. Consulte Como gravar funções em segundo plano para mais detalhes.

Exemplo:

Node.js

/**
 * Background Cloud Function to be triggered by Pub/Sub.
 * This function is exported by index.js, and executed when
 * the trigger topic receives a message.
 *
 * @param {object} message The Pub/Sub message.
 * @param {object} context The event metadata.
 */
exports.helloPubSub = (message, context) => {
  const name = message.data
    ? Buffer.from(message.data, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);
};

Python

def hello_pubsub(event, context):
    """Background Cloud Function to be triggered by Pub/Sub.
    Args:
         event (dict):  The dictionary with data specific to this type of
                        event. The `@type` field maps to
                         `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
                        The `data` field maps to the PubsubMessage data
                        in a base64-encoded string. The `attributes` field maps
                        to the PubsubMessage attributes if any is present.
         context (google.cloud.functions.Context): Metadata of triggering event
                        including `event_id` which maps to the PubsubMessage
                        messageId, `timestamp` which maps to the PubsubMessage
                        publishTime, `event_type` which maps to
                        `google.pubsub.topic.publish`, and `resource` which is
                        a dictionary that describes the service API endpoint
                        pubsub.googleapis.com, the triggering topic's name, and
                        the triggering event type
                        `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
    Returns:
        None. The output is written to Cloud Logging.
    """
    import base64

    print("""This Function was triggered by messageId {} published at {} to {}
    """.format(context.event_id, context.timestamp, context.resource["name"]))

    if 'data' in event:
        name = base64.b64decode(event['data']).decode('utf-8')
    else:
        name = 'World'
    print('Hello {}!'.format(name))

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"context"
	"log"
)

// PubSubMessage is the payload of a Pub/Sub event.
// See the documentation for more details:
// https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
type PubSubMessage struct {
	Data []byte `json:"data"`
}

// HelloPubSub consumes a Pub/Sub message.
func HelloPubSub(ctx context.Context, m PubSubMessage) error {
	name := string(m.Data) // Automatically decoded from base64.
	if name == "" {
		name = "World"
	}
	log.Printf("Hello, %s!", name)
	return nil
}

Java


import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import com.google.events.cloud.pubsub.v1.Message;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloPubSub implements BackgroundFunction<Message> {
  private static final Logger logger = Logger.getLogger(HelloPubSub.class.getName());

  @Override
  public void accept(Message message, Context context) {
    String name = "world";
    if (message != null && message.getData() != null) {
      name = new String(
          Base64.getDecoder().decode(message.getData().getBytes(StandardCharsets.UTF_8)),
          StandardCharsets.UTF_8);
    }
    logger.info(String.format("Hello %s!", name));
    return;
  }
}

Funções do CloudEvent

As funções orientadas a eventos escritas para os ambientes de execução .NET, Ruby e PHP são conhecidas como funções do CloudEvent. Consulte Como escrever Funções do CloudEvent para mais detalhes.

Exemplo:

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.PubSub.V1;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace HelloPubSub
{
    public class Function : ICloudEventFunction<MessagePublishedData>
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
        {
            string nameFromMessage = data.Message?.TextData;
            string name = string.IsNullOrEmpty(nameFromMessage) ? "world" : nameFromMessage;
            _logger.LogInformation("Hello {name}", name);
            return Task.CompletedTask;
        }
    }
}

Ruby

require "functions_framework"
require "base64"

FunctionsFramework.cloud_event "hello_pubsub" do |event|
  # The event parameter is a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  name = Base64.decode64 event.data["message"]["data"] rescue "World"

  # A cloud_event function does not return a response, but you can log messages
  # or cause side effects such as sending additional events.
  logger.info "Hello, #{name}!"
end

PHP


use Google\CloudFunctions\CloudEvent;

function helloworldPubsub(CloudEvent $event): void
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    $cloudEventData = $event->getData();
    $pubSubData = base64_decode($cloudEventData['message']['data']);

    if ($pubSubData) {
        $name = htmlspecialchars($pubSubData);
    } else {
        $name = 'World';
    }

    $result = 'Hello, ' . $name . '!';
    fwrite($log, $result . PHP_EOL);
}

Como estruturar o código-fonte

Para que o Cloud Functions encontre a definição da sua função, cada ambiente de execução tem requisitos de estruturação para o código-fonte: Em geral, recomendamos dividir as bases de código grandes e multifuncionais em bases pequenas por função para reduzir a complexidade do código e as contagens de dependência por função.

Node.js

Para os tempos de execução do Node.js, o código-fonte da função precisa ser exportado de um módulo Node.js, que o Cloud Functions carrega usando uma chamada require(). Para determinar qual módulo carregar, o Cloud Functions usa o campo main no seu arquivo package.json. Se o campo main não for especificado, o Cloud Functions carregará o código de index.js.

Por exemplo, as seguintes configurações do código-fonte são válidas:

  • Um único index.js localizado no diretório raiz da função que exporta uma ou mais funções:

    .
    └── index.js
    
  • Um arquivo index.js que importa o código de um arquivo foo.js e, em seguida, exporta uma ou mais funções:

    .
    ├── index.js
    └── foo.js
    
  • Um arquivo app.js que exporta uma ou mais funções, com um arquivo package.json que contém "main": "app.js":

    .
    ├── app.js
    └── package.json
    

Python

Em um ambiente de execução do Python, o ponto de entrada da função precisa ser definido em um arquivo de origem do Python chamado main.py localizado no diretório raiz das funções.

Por exemplo, as seguintes configurações do código-fonte são válidas:

  • Um único arquivo main.py no diretório raiz da função que define uma ou mais funções:

    .
    └── main.py
    
  • Um arquivo main.py com um arquivo requirements.txt que especifica dependências:

    .
    ├── main.py
    └── requirements.txt
    
  • Um arquivo main.py que importa o código de uma dependência local:

    .
    ├── main.py
    └── mylocalpackage/
        ├── __init__.py
        └── myscript.py
    

Go

Para o ambiente de execução do Go, sua função precisa estar em um pacote Go na raiz do projeto. Sua função não pode estar em package main. Subpacotes só são compatíveis quando se usa módulos Go.

Por exemplo, as seguintes configurações do código-fonte são válidas:

  • Um pacote na raiz do projeto que exporta uma ou mais funções:

    .
    └── function.go
    
  • Um pacote na raiz do projeto que importa código de um subpacote e exporta uma ou mais funções:

    .
    ├── function.go
    ├── go.mod
    └── shared/
        └── shared.go
    
  • Um pacote na raiz do projeto com um subdiretório que define um package main:

    .
    ├── cmd/
    |   └── main.go
    └── function.go
    

Java

Para o ambiente de execução do Java, crie um diretório de função de nível superior contendo um subdiretório src/main/java/ e um arquivo pom.xml. Recomendamos colocar testes em um subdiretório src/test/java/.

.
├── pom.xml
└── src/
    ├── main/
    |   └── java/
    |       └── MyFunction.java
    └── test
        └── java/
            └── MyFunctionTest.java

Se o arquivo .java declarar um pacote (por exemplo, functions), sua hierarquia de diretórios ficaria assim:

.
├── pom.xml
└── src/
    ├── main/
    |   └── java/
    |       └── functions/
    |               └── MyFunction.java
    └── test/
        └── java/
                └── functions/
                    └── MyFunctionTest.java

Se a função estiver definida em um pacote específico como a maioria das funções Java, ela precisará ser especificada como parte do valor --entry-point no momento da implantação.

Como agrupar várias funções

Se você estiver pensando em agrupar várias funções em um único projeto, esteja ciente de que cada função pode acabar compartilhando o mesmo conjunto de dependências. No entanto, algumas das funções podem não precisar de todas as dependências compartilhadas.

Recomendamos colocar cada função em seu próprio diretório de nível superior, conforme mostrado acima, com seu próprio subdiretório src/main/java e arquivo pom.xml. Essa abordagem minimiza o número de dependências necessárias para uma função específica, o que reduz a quantidade de memória necessária para a função.

Além disso, funções separadas facilitam a especificação de uma única função ao executar funções localmente por meio do Functions Framework. Isso pode ser útil para desenvolvimento e testes locais.

C#

Para o ambiente de execução do .NET, é possível estruturar os projetos como com qualquer outro código-fonte .NET.

Quando você usa modelos para criar uma função C#, ele cria os seguintes arquivos no mesmo nível no sistema de arquivos:

  • Um arquivo de origem de função chamado Function.cs.
  • Um arquivo de projeto com uma extensão .csproj.

Por exemplo:

.
├── Function.cs
└── HelloHttp.csproj

O mesmo padrão se aplica quando você usa os modelos para criar funções F# e Visual Basic. Para F#, o nome do arquivo é Function.fs e o arquivo do projeto tem a extensão .fsproj. Para o Visual Basic, o nome do arquivo é CloudFunction.vb e o arquivo do projeto tem a extensão .vbproj.

No entanto, esses padrões são apenas convenções, e não requisitos. É possível estruturar o código da mesma forma que qualquer outro projeto C#, F# ou Visual Basic, que pode conter vários arquivos de origem, recursos e assim por diante.

Além disso, se você implementar apenas uma única função por projeto, poderá começar a hospedar essa função sem especificar o nome dela, o que pode simplificar o desenvolvimento local e a depuração.

Ruby

Para o ambiente de execução do Ruby, o ponto de entrada da sua função precisa ser definido em um arquivo de origem do Ruby chamado app.rb, localizado no diretório raiz da função. Além disso, as dependências, incluindo pelo menos a gem functions_framework, precisam ser listadas em um Gemfile e um arquivo Gemfile.lock associado, também localizado no diretório raiz da função. A função pode incluir arquivos Ruby adicionais no diretório raiz ou nos subdiretórios.

Por exemplo, as seguintes configurações do código-fonte são válidas:

.
├── Gemfile
├── Gemfile.lock
└── app.rb

.
├── Gemfile
├── Gemfile.lock
├── app.rb
└── lib/
    └── my_class.rb

PHP

Para o ambiente de execução PHP, crie um diretório de função de nível superior que tenha um arquivo index.php que contenha a implementação da função.

O arquivo composer.json declara dependências. Ao executar o comando composer require google/cloud-functions-framework conforme descrito em Como especificar dependências no PHP, ele cria um diretório vendor/ no diretório do código de função que contém as dependências. O arquivo composer.lock bloqueia as dependências da função, o que significa que a função retém um registro exato das versões de dependência que foram instaladas. Se você executar composer install novamente no futuro, ele usará as versões de dependência especificadas no arquivo composer.lock.

Recomendamos colocar testes em um diretório test/.

.
├── index.php
├── composer.json
├── composer.lock
├── vendor/
└── test/

Como especificar dependências

Você especifica as dependências de sua função de maneira idiomática com base no ambiente de execução usado. Para mais detalhes, consulte a página apropriada:

Como nomear Cloud Functions

O Cloud Functions tem uma propriedade "name" que é definida no momento da implantação e, uma vez definida, não pode ser alterada. O nome de uma função é usado como seu identificador e precisa ser exclusivo em uma região. Consulte a documentação de implantação para mais detalhes.

Próximas etapas

Faça um teste

Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho do Cloud Functions em situações reais. Clientes novos também ganham US$ 300 em créditos para executar, testar e implantar cargas de trabalho.

Faça uma avaliação gratuita do Cloud Functions