この記事はデベロッパー アドボケート、Doug Stevenson による The Firebase Blog の記事 "Cloud Functions Realtime Database Triggers Are Now More Efficient" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Cloud Functions for Firebase チームとの仕事で特にやりがいがあるのは、モバイルアプリから Firebase がホストするフル マネージドなバックエンドにロジックを移行するデベロッパーの皆さんをお手伝いできる事です。ロジックはわずか数行の JavaScript コードで組み込むことができ、Realtime Database の内容が変更された際に自動でアクションを実行できます。皆さんが開発するものを見るのは本当に楽しいことです。
データベースの変更にはさまざまなタイプがありますが、Cloud Next 2017 で Cloud Functionsが最初に発表されたとき、トリガーは 1 つしかありませんでした。このトリガーは
ここでの注意点として、イベント オブジェクトの DeltaSnapshotをチェックし、書き込まれたロケーションに新しいデータや以前のデータが存在するかどうかを確認しなければなりません。その理由は、
改善された機能
そこでうれしいお知らせです。Cloud Functions のデータベース トリガーで、この余分なチェックが不要になりました。firebase-functions モジュール バージョン 0.5.9 以降では、新たに 3 種類のデータベース トリガー
同じロケーションのデータが更新されるたびにこの機能が再び呼び出されることを心配する必要はありません。これによって、コードが読みやすくなるだけでなく、運用費用も削減できます。
なお、
あるロケーションのデータに対して
次のコードについて考えてみましょう。この機能を使ってメッセージが書き込まれた際に lastUpdated プロパティを更新します。
最初は問題なさそうに思えますが、とても重要な点が抜けています。この機能は呼び出されたロケーションにデータを書き戻しているので、実は もう一度、
少しロジックを追加すれば、無限ループを防ぐことができます。
今回紹介した新しいトリガーを利用する場合は、firebase-functions モジュールを 0.5.9 にアップデートしてください。これらのアップデートについての詳しい情報は、こちらのドキュメントをご覧ください。
Reviewed by Takuo Suzuki - Developer Relations Team
Cloud Functions for Firebase チームとの仕事で特にやりがいがあるのは、モバイルアプリから Firebase がホストするフル マネージドなバックエンドにロジックを移行するデベロッパーの皆さんをお手伝いできる事です。ロジックはわずか数行の JavaScript コードで組み込むことができ、Realtime Database の内容が変更された際に自動でアクションを実行できます。皆さんが開発するものを見るのは本当に楽しいことです。
データベースの変更にはさまざまなタイプがありますが、Cloud Next 2017 で Cloud Functionsが最初に発表されたとき、トリガーは 1 つしかありませんでした。このトリガーは
onWrite()
コールバックを使って指定するもので、どのような変更が行われたのかを判断するのは、コードを書いたエンジニアの責任でした。たとえば、アプリにチャットルームがあり、新しいメッセージが届いた際に Firebase Cloud Messaging を使ってそのチャットルームのユーザーに通知を送ることを考えてみましょう。これを実装するには、次のようなコードを記述します。exports.sendNotification = functions.database
.ref("/messages/{messageId}").onWrite(event => {
const dsnap = event.data // a DeltaSnapshot that describes the write
if (dsnap.exists() && !dsnap.previous.exists()) {
// This is a new message, not a change or a delete.
// Send notifications with FCM...
}
})
ここでの注意点として、イベント オブジェクトの DeltaSnapshotをチェックし、書き込まれたロケーションに新しいデータや以前のデータが存在するかどうかを確認しなければなりません。その理由は、
onWrite()
は、該当するロケーションに対する すべてのデータの変更に対して呼び出されるからです。これには、メッセージの新規作成、変更、削除が含まれます。しかし、この機能は 新しいメッセージのみを対象とするものです。そのため、書き込みが更新や削除ではないことを確認しなければなりません。更新や削除が発生した場合、この機能は呼び出されてすぐに処理を終えることになります。このように余分な機能が実行されると、そのぶん費用がかさみます。皆さんも、役に立たない処理に対して課金されたくはないはずです。改善された機能
そこでうれしいお知らせです。Cloud Functions のデータベース トリガーで、この余分なチェックが不要になりました。firebase-functions モジュール バージョン 0.5.9 以降では、新たに 3 種類のデータベース トリガー
onCreate()
、onUpdate()
、onDelete()
を記述できます。これらのトリガーは変更のタイプを認識し、対象となる変更が発生したときにのみ実行されます。したがって、先ほどの機能は次のように書き換えることができます。 exports.sendNotification = functions.database
.ref("/messages/{messageId}").onCreate(event => {
// Send notifications with FCM...
})
同じロケーションのデータが更新されるたびにこの機能が再び呼び出されることを心配する必要はありません。これによって、コードが読みやすくなるだけでなく、運用費用も削減できます。
なお、
onWrite()
はなくなったわけではないことに注意してください。このトリガーも引き続き自由に使うことができます。onWrite()
と onUpdate()
の無限ループを防ぐ あるロケーションのデータに対して
onWrite()
を使って追加や変更を行うと、onWrite()
で行ったデータの変更によって、もう 1 度 onWrite()
が呼ばれることを意識するようにしてください( すべての書き込みが書き込みとして扱われるためです)。 次のコードについて考えてみましょう。この機能を使ってメッセージが書き込まれた際に lastUpdated プロパティを更新します。
exports.lastUpdate = functions.database
.ref("/messages/{messageId}").onWrite(event => {
const msg = event.data.val()
msg.lastUpdated = new Date().getTime()
return event.data.adminRef.set(msg)
})
最初は問題なさそうに思えますが、とても重要な点が抜けています。この機能は呼び出されたロケーションにデータを書き戻しているので、実は もう一度、
onWrite()
が呼ばれることになります。これが書き込みの無限ループを引き起こすことはおわかりでしょう。そのため、2 回目の呼び出しを防ぐ方法が必要になります。 onUpdate()
で機能を実装する場合も同じ問題が起きます。このようなケースの解決策の 1 つとして、既存の lastUpdated の値を確認し、現在日時から 30 秒以内である場合、処理を行わないことが考えられます。そのため、onUpdate()
を使ってこの機能を書き直す場合、次のようになります。 exports.lastUpdate = functions.database
.ref("/messages/{messageId}").onUpdate(event => {
const msg = event.data.val()
const now = new Date().getTime()
if (msg.lastUpdated > now - (30*1000)) {
return
}
msg.lastUpdated = now
return event.data.adminRef.set(msg)
})
少しロジックを追加すれば、無限ループを防ぐことができます。
今回紹介した新しいトリガーを利用する場合は、firebase-functions モジュールを 0.5.9 にアップデートしてください。これらのアップデートについての詳しい情報は、こちらのドキュメントをご覧ください。
Reviewed by Takuo Suzuki - Developer Relations Team