i-Company CLUBをサーバレスで支えるCloud Functions for Firebaseの導入からデプロイまで

i-Company CLUBの開発と運用を担当している、パートナーの@gonjittiです。

<br/>

i-Company CLUBとは、アプリを通じてユーザーの習慣化を支援するコーチングサービスです。
日常を変えるのは、小さな習慣です。
習慣は複利で効果をもたらします。
ご興味のある方はぜひ[こちら](https://www.aviva.co.jp/lp_icompany/)より参照ください。


i-Company CLUBはバックエンドがサーバレスアーキテクチャとなっており、Firebaseで構築しています。  
今回はFirebaseのうち、アプリのAPI及びバッチ処理で利用しているCloud Functions for Firebaseの紹介を行います。

## Cloud Functions for Firebaseとは


Cloud Functions for Firebase(以下Cloud Functionsと記載)とは、Firebaseの機能のうち、バックエンド部分をサーバレスに実現するプロダクトです。


[![firebase-image](https://res.cloudinary.com/gonjitti/image/upload/v1575968075/irodori/LMI_blog/Screen_Shot_2019-12-10_at_17.53.33_hcb0qi.png)](https://firebase.google.com/products/functions/)  
<cite>https://firebase.google.com/products/functions/</cite>


HTTPS、指定した日時、DBへの値の新規追加や更新をトリガーにNodeJSのプログラムを実行する事ができ、料金は呼び出し回数や実行中に利用したリソース分だけで機能を実現することができます。


類似サービスとしてはAWS Lambdaなどがあり、AWSがやや先行していたきらいがありますが、現在のCloud FunctionsはFirebaseのシンプルなアーキテクチャに則ってインフラの初心者でも大変扱いやすい構成となっており、本番運用でも安定して利用できる水準になっています。


今回は初めて関数をアップロードするまでの過程を紹介しますので、扱いやすさを体感いただけますと幸いです。
※今回リリースする関数は定期実行の関数(Cloud PubSubを使用します)であり、Firebaseのプランは従量制のプランに変更する必要があります。無料枠の範囲内で今回の関数は利用可能なので、差し支えなければ試す前にFirebaseの料金プランをBlazeプランにアップグレードしておいてください。

![app-team-shuffle-image](https://res.cloudinary.com/gonjitti/image/upload/v1576113767/irodori/LMI_blog/Screen_Shot_2019-12-12_at_10.19.45_crp4g9.png)


## チームシャッフルバッチの実装から本番リリースまで

### チームシャッフルバッチとは


今回はi-Company CLUBのメイン機能である「チーム機能」から、毎週日曜日のメンバーシャッフル機能を本番にリリースするまでの過程を書いて行きます。


チームシャッフル機能とは、アプリを利用しているユーザーが毎週シャッフルされ、5名ごとのチームに配属されます。
5名ごとのチームメンバーはお互いの習慣の実施状況に対して、「いいね」や「スタンプ」を通じたコミュニケーションが可能です。お互いの習慣を応援し合う機能です。


![app-team-shuffle-image](https://res.cloudinary.com/gonjitti/image/upload/c_thumb,w_200,g_face/v1575968475/irodori/LMI_blog/0x0ss-P3_uwifgv.jpg)


今回はこのシャッフル関数(teamShuffle)をリリースするまでの過程を説明します。

### 手順のサマリ


手順のサマリは下記です。
Cloud Functions公式のドキュメントの[スタートガイド](https://firebase.google.com/products/functions/)も並行して参照ください。

1. Firebase CLIとNode.jsの設定
2. Firebaseツールの認証とプロジェクトの初期化
3. プロジェクトルートのfunctions/配下にあるindex.jsに関数を作成
4. ローカルで動作チェック
5. 本番環境にデプロイ
6. 管理画面上からデプロイチェック


#### 1. Firebase CLIとNode.jsの設定

まずは今運用しているプロジェクトのルートディレクトリにてNodeJSとFirebase CLIを設定しましょう。


CloudFunctionsではNode.jsは8と10がサポートされています(基本は8)。またCLIインストールのため、npmが必要です。事前にNode.jsとnpmのセットを実施の上、下記のコマンドでFirebaseCLIを設定ください。

```
npm install -g firebase-tools
```

#### 2. Firebaseツールの認証とプロジェクトの初期化

次はインストールしたfirebase-toolsの認証とプロジェクトルートディレクトリの初期化処理です。


firebase-toolsのログインは、下記コマンドで実行出来ます。実行するとGoogleのログイン画面がポップアップで表示され、ログインを求められます。ログインすると、実際にfirebase-toolsが利用できるようになります。

```
firebase login
```

引き続き、始めてCloud Functionsをプロジェクトで利用する場合は、プロジェクトのルートディレクトリにて下記コマンドを実行しましょう。実行後はコマンドラインの指示に従ってプロジェクト名やプロジェクト名や利用する言語を選択し、設定が完了すると開発を始めることができます。

```
firebase init functions
```

するとプロジェクト配下に下記の構成でディレクトリが構築されます。

```
myproject
 +- .firebaserc    # firebaseのプロジェクトを自動で切り替える設定値などを持る隠しファイル
 |
 |
 +- firebase.json  # 各Firebaseプロジェクトの環境変数を内包するファイル(APIキーなど)
 |
 +- functions/     # すべてのFunctionsを保持するディレクトリ
      |
      +- .eslintrc.json  # JavaScriptのLintファイル.
      |
      +- package.json  # npm パッケージファイル
      |
      +- index.js      # CloudFunctionsを実際に書くコード
      |
      +- node_modules/ # npmのpackage.jsonで設定したパッケージのインストール後配置ディレクトリ
```


#### 3. プロジェクトルートのfunctions/配下にあるindex.jsに関数を作成

次はindex.jsに関数を作成しましょう。ここはNode.jsとしてコードを書くことが出来ます。今回は毎週userをシャッフルしてチームを作成するようなバッチを書くので、下記のようなイメージで毎週日曜日の23:00に実行されるようなコードを書きます。

```js
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

/**
 * メンバーをシャッフルしてチームを作成する
 */
exports.TeamShuffle = functions.pubsub
  .schedule('every sunday 23:00')
  .timeZone('Asia/Tokyo')
  .onRun(() => {
    try {
      console.log('User shuffle and create team start');
      return shuffle();
    } catch (error) {
      return console.error('User shuffle and create team failed', error);
    }
  });

/**
 * シャッフルを実行(userIdの配列でチームを作成することを想定)
 */
const shuffle = () => {
  console.log('シャッフルを開始');
  console.log('---');
  const teams = [['test1','test2'], ['test3', 'test4']]; // シャッフル後のUserId配列
  console.log('シャッフルを完了');
  return teams;
};
```


今回は毎週日曜日の23:00に実行されるようなコードを記載していますが、HTTPSトリガーやDBトリガーなど、様々な形でコードを呼び出すことができます。詳細は[ドキュメント](https://firebase.google.com/products/functions/)を参照ください。


#### 4. ローカルで動作チェック


関数はローカルで動作チェックすることができます。実は今回の定期実行のコードでローカルでテストができませんが、HTTPSトリガーで実行するように下記書き換えた上で手元で実行することができます。

```js
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

/**
 * メンバーをシャッフルしてチームを作成する
 */
exports.TeamShuffle = functions.https // <-pubsubをHTTPSトリガーに記載変更
  .onRequest(() => {
    try {
      console.log('User shuffle and create team start');
      return shuffle();
    } catch (error) {
      return console.error('User shuffle and create team failed', error);
    }
  });

/**
 * シャッフルを実行(userIdの配列でチームを作成することを想定)
 */
const shuffle = () => {
  console.log('シャッフルを開始');
  console.log('---');
  const teams = [['test1','test2'], ['test3', 'test4']]; // シャッフル後のUserId配列
  console.log('シャッフルを完了');
  return teams;
};
```


ローカルでの動作テストの方法は下記コマンドです。コマンドと実行例を示します。

```
firebase emulators:start --only functions
```


- ローカルでのエミュレート実行とHTTPリクエストスタンバイの例

```
~/w/l/functions> firebase emulators:start --only functions
i  Starting emulators: ["functions"]
✔  functions: Emulator started at http://localhost:5001
i  functions: Watching "/Users/{ユーザー名}/workspace/hogehoge/functions" for Cloud Functions...
i  Your code has been provided a "firebase-admin" instance.
i  functions: HTTP trigger initialized at http://localhost:5001/hogehoge/us-central1/TeamShuffle
```

それでは実際に実行してみましょう。`http://localhost:5001/hogehoge/us-central1/TeamShuffle`を実行するとfirebaseの管理画面のコンソールにログが表示され、正常に動作することがわかります。

#### 5. 本番環境にデプロイ


いよいよ動作確認が取れた場合は、実際にFirebaseにデプロイを実行します。
デプロイ方法にはfunction全関数をリリースする場合と各関数をリリースする場合があります。

- CLIから`firebase deploy --only functions`を実行すると、index.jsから参照できる全ての関数をdeployします。
- 特定の関数だけをdeployしたい時は`firebase deploy --only functions:addMessage,functions:makeUppercase`のように関数名を指定して実行します。


#### 6. 管理画面上からデプロイチェック

リリースが正常に完了すると、Firebaseの管理画面のFunctionsタブから実際に関数が登録されていることが確認できます。
Functions全体でログの確認や正常性の確認が行えることに加え、個別の関数でもログを追うことが可能です。
過去ログは自動的にGoogleCloudPlatform内で長期保管され、保管期間に関しても設定が可能です。

![function-image](https://res.cloudinary.com/gonjitti/image/upload/v1575971167/irodori/LMI_blog/Screen_Shot_2019-12-10_at_18_41_43_u19ws5.png)


## 総括


以上の手順を踏まえると、これまでcrontabを設定してサーバ上で動作させていたバッチと同等の機能をサーバーレスで実現することができました。これにより、低コストと高メンテナビリティを両立したインフラを構築でき、将来の機能追加や修正に対しても柔軟に対応することが出来ています。


CloudFunctionsは今回紹介したバッチとしての活用以外にも、アプリのAPIやBigQueryへのデータ連携、DBのバックアップの実行なども可能なので、もしこれからアプリケーションを構築するにあたって省力運用を実現したい場合は、Firebaseを検討してみてください。