Cloud Functions+Node.jsを秒速でローカル実行する備忘録

[PONOS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ponos)の12日目の記事です。

昨日は[@sumborn](https://qiita.com/sumborn)さんでした。

# はじめに
Cloud Functionsを開発する場合、当然ローカルで実行させたくなります。その手順は[Googleの公式](https://cloud.google.com/functions/docs/running/overview?hl=ja)に載っています。
しかし、久々にFunctionsを触る時に "どうだったかな？" となることがあるので、今回は最低限の情報だけで秒速でローカル実行できる手順のみ記しておこうと思います。

# プロジェクト準備
1. とりあえず空のフォルダを作る
1. package.jsonを用意する。
    なんでもいいですが、npmを使って用意しました。ここではエントリーとなるポイントはindex.jsとしておいてください。
    ```console
    npm init
    ```
1. Functions Frameworkパッケージを入れておく
    ```console
    npm install --save-dev @google-cloud/functions-framework
    ```
# ローカルで動作させる
Cloud Functionsにはいくつかの種類の関数があります。
今回はとにかく秒速で動作させることが目的なので、余計な情報は省いて、よく使う以下のパターンごとに描きます。

1. HTTP
1. Pub/Sub
1. Cloud Storage

## 第一世代と第二世代について
HTTP以外をフックするケースでは、実行ランタイムとCloud Functionsの世代ごとに関数のタイプが異なります。
今回はNode.jsを対象としているため、Node.jsで言えば第一世代は[バックグラウンド関数](https://cloud.google.com/functions/docs/writing/write-event-driven-functions?hl=ja#background-functions)、第二世代は[CloudEvent関数](https://cloud.google.com/functions/docs/writing/write-event-driven-functions?hl=ja#cloudevent-functions)となります。

今回の手順は第一世代をベースに書きますが、基本的な手順は第二世代も変わりませんので、詳しくは[公式ページ](https://cloud.google.com/functions/docs/running/function-frameworks?hl=ja)をご確認ください。

## HTTP編
### 1. package.jsonのstartを書き換える
```json
  "scripts": {
    "start": "npx functions-framework --target=execute --signature-type=http"
  }
```
### 2. index.jsを作る
```javascript
exports.execute = async (req, res) => {
  res.status(200).send('hello world.');
}
```
### 3. ローカル起動する
```console
npm start
```
### 4. curlで呼び出す
普通にHTTPのリクエストを送信するだけです。
```console
curl http://localhost:8080
```
### 結果
コンソールに以下が返されると思います。
```
hello world.
```

## Pub/Sub（バックグラウンド関数）編
Node.jsの場合、第一世代のCloudFunctionsがこちらに当たります。

### 1. package.jsonのstartを書き換える

```json
  "scripts": {
    "start": "npx functions-framework --target=execute --signature-type=event"
  }
```
### 2. index.jsを作る
```javascript
exports.execute = async (event, context) => {
  const message = Buffer.from(event.data, 'base64').toString();
  console.log(message);
  console.log(event.attributes.key1);
}
```
### 3. ローカル起動する
```console
npm start
```
### 4. curlで呼び出す
以下のフォーマットでcurlを用いて呼び出します。
data部が、Pub/Subから送信するメッセージのデータになります。
data.dataはBase64エンコードされている点に注意です。
```console
curl localhost:8080 \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
        "context": {
          "eventId":"1144231683168617",
          "timestamp":"2020-05-06T07:33:34.556Z",
          "eventType":"google.pubsub.topic.publish",
          "resource":{
            "service":"pubsub.googleapis.com",
            "name":"projects/sample-project/topics/gcf-test",
            "type":"type.googleapis.com/google.pubsub.v1.PubsubMessage"
          }
        },
        "data": {
          "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage",
          "attributes": {
             "key1":"hoge"
          },
          "data": "aGVsbG8gd29ybGQu"
        }
      }'
```
### 結果
コンソールに以下が出力されると思います。
```
hello world.
hoge
```

## Cloud Storage（バックグラウンド関数）編
Node.jsの場合、第一世代のCloudFunctionsがこちらに当たります。

### 1. package.jsonのstartを書き換える

```json
  "scripts": {
    "start": "npx functions-framework --target=execute --signature-type=event"
  }
```
### 2. index.jsを作る
```javascript
exports.execute = async (event, context) => {
  console.log(`file: ${event.name} bucket: ${event.bucket}`);
}
```
### 3. ローカル起動する
```console
npm start
```
### 4. curlで呼び出す
以下のフォーマットでcurlを用いて呼び出します。
data.bucketがバケット名、data.nameがファイル名になります。なお、ファイル名は実際にはファイルのフルパスが入ってきます。
```console
curl localhost:8080 \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
        "context": {
          "eventId": "1147091835525187",
          "timestamp": "2020-04-23T07:38:57.772Z",
          "eventType": "google.storage.object.finalize",
          "resource": {
             "service": "storage.googleapis.com",
             "name": "projects/_/buckets/MY_BUCKET/MY_FILE.txt",
             "type": "storage#object"
          }
        },
        "data": {
          "bucket": "MY_BUCKET",
          "contentType": "text/plain",
          "kind": "storage#object",
          "md5Hash": "...",
          "metageneration": "1",
          "name": "MY_FILE.txt",
          "size": "352",
          "storageClass": "MULTI_REGIONAL",
          "timeCreated": "2020-04-23T07:38:57.230Z",
          "timeStorageClassUpdated": "2020-04-23T07:38:57.230Z",
          "updated": "2020-04-23T07:38:57.230Z"
        }
      }'
    
```
### 結果
コンソールに以下が出力されると思います。
```
file: MY_FILE.txt bucket: MY_BUCKET
```

# まとめ
正直なところ公式ドキュメントも難しい内容ではないため、そちらを読んでいただいても全く問題ないかと思います。
とはいえこうして最短手順で書いてみると、ものすごく簡単に開発できる環境になっているなと感じます。どうだっけな？と感じた時のメモとしてお読みいただければ幸いです。

明日は[@iixd_pog](https://qiita.com/iixd_pog)さんです。