やりたいこと
- functionsから別のfunctionsをキックしたい
先に代替案
firebase-queue というものがあるので、条件に合えばこれを使っても良さそう
- firebase realtime databaseを利用している
- 4年くらい更新されていないので怖い
ユースケース例
この記事では下記のような挙動を例にします
- チャットアプリ
- ユーザーAがメッセージを投稿する
- 同じチャットルームに属するユーザーB, C, Dにpush通知が届く
具体的には下記のような処理フローになります
- ユーザーAがメッセージを投稿するとfirestoreにドキュメントがinsertされる
- ドキュメント作成のイベントを受けて、functionsAがキックされる
- functionsAの中で
- 同じルームのメンバー一覧を取得
- メンバーそれぞれについて、push通知を発行するfunctionBをキックする
functionA
実装のイメージです
// ポイント1: functionsの中だけど、クライアント用のSDKを利用している
import firebaseClient from "firebase";
firebaseClient.initializeApp(client_sdk_config);
const functionsClient = firebaseClient
.app()
.functions("asia-northeast1");
// ポイント2: timeoutを指定することで、functionsBをキックするけど結果を待たない、ということができる
const functionB = functionsClient.httpsCallable(
"functionB",
{ timeout: 1000 } // msec
);
const pushTargetUsers = ["user-B", "user-C", "user-D"];
const pushSendTasks = pushTargetUsers.map(async (userId) => {
try {
await functionB(userId);
} catch (error) {
if (e.code === "deadline-exceeded") {
// functionBの完了は待たないので、timeoutエラーは無視する
return;
}
throw e;
}
});
await Promise.all(pushSendTasks);
ポイント
-
client用のSDKを使う
- functions内部では普通はadmin SDKしか利用しない
- しかしadmin SDKにはfunctionsを呼び出す機能がない
- なのでclient用のSDKを利用することでfunctionsを呼び出す
-
functionsを呼び出すときにtimeout値を必要に応じてセットする
- timeoutをセットしない場合
- functionBが完了するまで待つことになる
- この場合、functionBの返り値を受け取ることができる
- 返り値を受け取りたい場合、わざわざ別functionに分離するメリットはあまりないので、このパターンはあまり利用機会がなさそう
- timeoutをセットする場合
- timeout以内にfunctionBが完了しなかった場合、
deadline-exceeded
のエラーがスローされる - そのエラーは想定内なので握りつぶす
- timeout以内にfunctionBが完了しなかった場合、
- timeoutをセットしない場合
-
functionsのタイムアウトは2種類ある
- x: functionそのものの実行時間のタイムアウト
- y: functionを呼び出す側がレスポンスを受け取るまでのタイムアウト
- これはfunctionを呼び出す側が、呼び出し時にセットする
↑xのタイムアウトを60秒、yのタイムアウトを10秒にセットしてfunctionを呼び出す、みたいなことができる
その場合、下記のような挙動になる
- 10秒以内に処理が完了した場合
- functionは正常にレスポンスを返す
- 10秒以上かかった場合
- 呼び出し側ではyのタイムアウトエラーがスローされる
- しかし、キックされたfunction自体は10秒を超えても動いており、60秒以内に処理が完了すれば正常終了となる
感想
AWS LambdaではSDKが同期呼び出し、非同期呼び出しなどをサポートしており、もっとシンプルに同じことがやれます
firebase functionsでも同じことがやりたいと思って試行錯誤してこの結論に辿り着いたのですが、SDKでサポートしてくれるとありがたいですね...
また、そもそもfunctionからfunctionを直接呼び出すというのはあまり良い手段ではなく、AWSではstep functionsを使うべき場面だと思います
firebaseでも同様に別の手段を模索するべきなのかなとちょっと思いました