GAミント至上主義

Web Monomaniacal Developer.

Firebase Functionsで関数ごとにファイルを分割し高速化とメンテナンス性向上も目指す

Firebase Functionsでは、基本的にはindex.jsにすべての関数を書くことになるので、数が増えるといろいろつらくなってきますが、2018秋アニメはAmazonでSAO新作とゴブリンスレイヤーを見ています。

ファイル分割については、ググればこんな感じのが見つかります。
javascript - How do I structure Cloud Functions for Firebase to deploy multiple functions from multiple files? - Stack Overflow

index.js:

const functions = require('firebase-functions');
const fooModule = require('./foo');
const barModule = require('./bar');

exports.foo = functions.database.ref('/foo').onWrite(fooModule.handler);
exports.bar = functions.database.ref('/bar').onWrite(barModule.handler);

普通にrequireして、exportsに追加する形です。

しかし、これだとどの関数が呼ばれてもindex.jsをすべて実行してしまい、無駄なモジュールをロードして起動が遅くなることがあるようです。
そのため下記記事ではCloud Functionsで設定される環境変数を使用して、読み込み分けを紹介されていました。

tech.ginco.io

index.js

if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'Func1') {
    exports.Func1 = require('./funcs/func1');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'Func2') {
    exports.Func2 = require('./funcs/func2');
}

デプロイ時にはprocess.env.FUNCTION_NAMEが設定されないため、それを利用して全部読み込まれるようにしているのが上手い。

動作はこれで問題ないですが、関数が増えてきた場合、if分とexportsを繰り返して書くためちょっとすっきりしません。
そこで下記のように関数名とファイルパスをオブジェクトとし、forで回してみました。

index.js

const funcs = {
  sendPostCard2Chat: './src/send-postcard-to-chat',
  replyPostCard2Yagish: './src/reply-postcard-to-yagish',
  notFound: './src/not-found',
  updateOmikujiStatistics: './src/update-omikuji-statistics'
}

loadFunctions = (funcsObj) => {
  for(let name in funcsObj){
    if(! process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) {
      exports[name] = require(funcsObj[name])
    }
  }
}

console.log('process.env.FUNCTION_NAME:', process.env.FUNCTION_NAME)
loadFunctions(funcs)
console.log('exports:', exports)

デプロイも問題なく終了します。

> functions@ lint /shanyang/app/functions
> eslint .

✔  functions: Finished running predeploy script.
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (95.95 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: updating Node.js 8 function sendPostCard2Chat(us-central1)...
i  functions: updating Node.js 8 function replyPostCard2Yagish(us-central1)...
i  functions: updating Node.js 8 function notFound(us-central1)...
i  functions: updating Node.js 8 function updateOmikujiStatistics(us-central1)...
✔  functions[updateOmikujiStatistics(us-central1)]: Successful update operation.
✔  functions[sendPostCard2Chat(us-central1)]: Successful update operation.
✔  functions[replyPostCard2Yagish(us-central1)]: Successful update operation.
✔  functions[notFound(us-central1)]: Successful update operation.

実行時のconsole.log('exports:', exports)のログも一つだけになりました。

10:31:29.014 午前 sendPostCard2Chat exports: { sendPostCard2Chat: { [Function: cloudFunctionNewSignature] run: [AsyncFunction] } }

動作も影響なさそうです。
まだいろいろ確認中なのでlogなども残ってますが、exportsを関数内で実行しても問題なく動くようです。
更にこの設定のオブジェクトとloadFunctions関数を別ファイルにすることも考えましたが、index.jsという名前的にここに一覧を書くのがよさそうだし、loadFunctionsをモジュール化して読み込むとindex.jsでは読まれないようで動きませんでした。もっと美しくできそうですが現時点はこんなところ。

あと設定をObjectではなく、Mapでやってもいいかなと思ったけどいいのかわからない。

変数をグローバルにキャッシュしておく

関数ごとにすべて分けるのが絶対に良いかというとそんな単純ではなく、下記の通り起動した実行環境(コンテナ?)は再利用されることがあるようです。
そのためメンテナンス性だけを考えるとファイルを分けた方がいいですが、速度を重視するようになるとこのグローバル変数も考慮する必要がありそうです。
ヒントとアドバイス  |  Firebase

Cloud Function の状態は、将来の呼び出しのために必ずしも保持されるわけではありません。しかし、Cloud Functions が以前の呼び出しの実行環境をリサイクルすることはよくあります。変数をグローバル スコープで宣言すると、その値は再計算せずに後続の呼び出しで再利用できるようになります。

まだ実際のコードは書いてませんが、絶対使うfirebase-funcstionsみたいなものはglobalオブジェクトに突っ込んでいいけど、関数によって使うか使わないか違うやつは、遅延評価をした方がいいとなると呼び出し時に上記のような書き方をする必要があり、コードが煩雑になるつらみもある。
開発初期はメンテナンス性重視で良く使うものはglobalに、開発が進んでパフォーマンスが必要になった箇所で遅延評価、みたいな感じが現実的だろうか。



改訂新版 Vue.jsとFirebaseで作るミニWebサービス (技術書典シリーズ(NextPublishing))

改訂新版 Vue.jsとFirebaseで作るミニWebサービス (技術書典シリーズ(NextPublishing))

WEB+DB PRESS Vol.105

WEB+DB PRESS Vol.105

  • 作者: 小笠原みつき,西村公宏,柳佳音,志甫侑紀,池田友洋,木村涼平,?橋優介,大塚雅和,飯塚直,吉川竜太,末永恭正,久保田祐史,浜田真成,穴井宏幸,大島一将,桑原仁雄,牧大輔,池田拓司,はまちや2,竹原,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/06/23
  • メディア: 単行本
  • この商品を含むブログを見る