Portable code migrating across Google Cloud's serverless platforms
Tags: googlecloud, googlecloudrun, cloudfunctions, functionsframework
Published: 2019-12-05 17:37:36

Google Cloud has a [number of options](https://cloud.google.com/hosting-options/) to run your code. We'll focus on three serverless platforms: Cloud Functions, App Engine, and Cloud Run, all of which automatically scale by traffic and bill by usage. The biggest difference between these platforms is the level of abstraction. We can deploy a function to Cloud Functions, an app to App Engine, or an app with a custom runtime (a Docker container) to Cloud Run.

The most abstract serverless hosting option is a function. It has no code except the core business logic of a specialized service. Two challenges in working with serverless functions include:

* Developing locally without a platform to convert requests to function calls
* Finding hosting alternatives when the infrastructure, runtime, or cost constraints of your platform are reached

Using a "Hello World" function with an international twist: "Hello Translate", we'll explore a new way to mitigate these challenges on Google Cloud.

## Standalone Functions with the Functions Framework

Like all Google Cloud Functions runtimes, the Node.js v10 runtime supports direct HTTP triggers while removing the overhead of HTTP routing from developers. Under the hood this magic is done by the open source [Node.js functions framework](https://github.com/GoogleCloudPlatform/functions-framework-nodejs). The framework installs as a normal dependency for your function and encapsulates [express.js](https://expressjs.com/) to provide clean and standardized HTTP request handling.

Because it provides an HTTP server you can run your function as a localhost web service, or deploy it to any web hosting platform.

> 1. Write business logic
> 2. Add a **Function Framework**
> 3. Deploy to a web host

Let's get to the code for "Hello Translate".

## Writing a Function

Create a new `npm` package and install the functions framework and the translate library:

```sh
npm init
npm install @google-cloud/functions-framework@1 @google-cloud/translate@5
```

(The `@1` and `@5` bits instruct `npm` to retrieve specific major versions of the libraries. It makes the code in this post less timeless, but more likely to work as long as these versions are compatible with Google Cloud.)

Cook up the code for "Hello Translate!":

{% gist https://gist.github.com/grayside/6999838f742e3c260e2577715a542126 file=index.js %}

Before we're ready to deploy HelloTranslate, let's modify the `package.json` to define a start script:

```
 "scripts": {
    "start": "functions-framework --target=helloTranslate"
 }
```

When `npm start` is run, the Node process will start a web server in the function framework package which is configured to use the target function above. This enables local development with no other special dependencies. The same mechanism will be used in Cloud Functions, App Engine, and Cloud Run.

## Getting setup to work with Google Cloud

Set up a development environment and configure your project to support the "Hello Translate" function:

* Create or select a Google Cloud Project. Add it to your shell environment with `export PROJECT=[Your Project ID]` to streamline the commands below.
* Install and authenticate with the [Cloud SDK](https://cloud.google.com/sdk/docs/quickstarts)
* Enable the the Translate API: `gcloud services enable translate.googleapis.com`

## Testing on Cloud Functions

Start by confirming this is works as a Cloud Function, let's deploy it!

```sh
gcloud functions deploy helloTranslate \
    --runtime=nodejs10 --region=us-central1 --trigger-http
```

Wait a couple minutes and we're ready to make a request. Use curl to ask for "Hello World!" in the default Spanish language:

```sh
curl https://us-central1-${PROJECT}.cloudfunctions.net/helloTranslate
Hola Mundo!
```

To learn more about how to use Cloud Functions, check out the [HTTP Functions how-to](https://cloud.google.com/functions/docs/writing/http#writing_http_helloworld-nodejs).

## Deploying a Function to App Engine

Now that we've confirmed the code works with Cloud Functions, let's see it work as an app on App Engine. App Engine allows more control over the instance scaling and behavior, and the requirement to deploy an HTTP server is met by the function framework. This means we can deploy code to App Engine without stopping for code changes.

Adapt **Hello Translate** to App Engine by following these steps:

1) Add an `app.yaml` configuration file to declare the runtime:

```yaml
runtime: nodejs10
```

2) Deploy the code

```sh
gcloud app deploy
```

3) Ask our production service for the latest translation of "Hello World!" in Yoruba:

```sh
curl https://${PROJECT}.appspot.com?lang=yo
Mo ki O Ile Aiye!
```

To learn more about how to use App Engine Standard, check out the [Building an App Quickstart](https://cloud.google.com/appengine/docs/standard/nodejs/building-app/).

## Deploying a Function to Cloud Run

Cloud Run is similar to App Engine in expecting a full HTTP app to be deployed. Unlike App Engine, Cloud Run is driven by containers. Cloud Run does not know if you are using javascript, java, or a [web server written in BASH](https://github.com/steren/awesome-cloudrun/blob/master/Dockerfile). Containers allow the developer to customize the runtime environment.

Adapt **Hello Translate** to Cloud Run by following these steps:

1) Add a Dockerfile. Copy the Node.js Dockerfile from the [Cloud Run quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy). Here's a streamlined copy for quick reading:

{% gist https://gist.github.com/grayside/6999838f742e3c260e2577715a542126 file=Dockerfile %}

2) Build the Container.

```sh
gcloud builds submit --tag=gcr.io/${PROJECT}/hello-translate
```

3) Deploy the service.

```sh
gcloud run deploy hello-translate \
    --image=gcr.io/${PROJECT}/hello-translate
```

4) Ask our production service for the latest translation of "Hello World!" in Japanese:

```sh
curl https://hello-translate-j6jxwetqdq-uc.a.run.app?lang=ja
"こんにちは世界"
```

To learn more about how to use Cloud Run, check out the [Build and Deploy Quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy).

## Helpful Abstraction!

The function framework is a good case of an abstraction. Code that makes sense to deploy as a function doesn't need much customization in HTTP handling, so why include the boilerplate anywhere that code is hosted? With an open source package, your functions-that-are-services code is more portable across compute platforms.

If your function is chafing at the runtime or concurrency limitations of Cloud Functions, consider using a [Functions Framework](https://github.com/GoogleCloudPlatform/functions-framework) to migrate your service to another hosting option such as [Cloud Run](https://cloud.google.com/run/docs). With a little more work, you can even bring over event-driven functions by configuring [Pub/Sub push subscriptions](https://cloud.google.com/run/docs/tutorials/pubsub).

## Read Onward

For an example application using a Functions Framework to be simultaneously deployable to Cloud Functions and Cloud Run, check out Grant Timmerman's [Pizza Map sample](https://github.com/GoogleCloudPlatform/cloud-tasks-pizza-map).