スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Firebase Remote Config の変更内容を Slack に通知する

こんにちは、@manicmaniac です。スタディサプリ iOS アプリ開発チームのエンジニアリングマネージャーをしています。

小ネタみたいな話ではありますが、Firebase Remote Config の変更を Slack に通知するようにしたらちょっと便利だったので記事にしようと思いました。

Firebase Remote Config とは

Firebase Remote Config が何か知らない方もいると思うので、公式サイトから引用すると、

Firebase Remote Config

Firebase Remote Config は、ユーザーにアプリのアップデートをダウンロードしてもらわなくても、アプリの動作や外観を変更できるクラウドサービスです。Remote Config を使用して、アプリの動作や外観を制御するためのアプリ内デフォルト値を作成できます。

です。まったくその通りなのですが、使っていないとイメージがつきにくいですね。

要するに Firebase SDK を通じて、なんらかの設定値をいろんなアプリに共有する、読み取り専用の KVS のようなものだと思っておくとだいたい合っていそうです。

これができると何が嬉しいのかというと、たとえば feature toggle のように特定の機能だけをリリースしたり、A/B テストのようにユーザーのセグメントごとに機能や UI を切り替えるということが柔軟にできます。

スタディサプリでの Firebase Remote Config の使い方

実際にスタディサプリでは Firebase Remote Config を以下のような用途で使っています。

  1. 機能を先に実装しておいて、期日になったらリリースするため
  2. 外的要因によって変更される変数を差し込むため
    • 例: アプリの最低サポートバージョンを差し込むことで、それより古いバージョンのユーザーにアップデート通知を出す
    • 例: アプリに表示されるバナー画像を入稿する
    • 例: アプリからウェブビューで参照している外部サービスの URL を設定する

かつては 特定のユーザー属性を持つユーザーにのみ機能を解放するため に使っていたこともありましたが、現在はあまり行われていません。

Firebase Remote Config が変更され、それがアプリに反映されるまでには若干のタイムラグがあるため、課金によって解放される機能など「条件を満たしたら即使えないと致命的になる機能」には使いにくいという点も注意が必要です。

変更内容を Slack に通知したいモチベーション

これまで少なくとも5年以上にわたって Firebase Remote Config を運用してきたのですが、たまに以下のような運用上の課題が発生していました。

  • 開発環境の Firebase Remote Config の値をデバッグ用途などで一時的に変更したことが共有されておらず、別チームの検証に支障が出る
  • バナー画像を JSON フォーマットで入稿しているが、バリデーションがないため JSON の文法エラーでバナーが表示されなくなる
  • 本番環境の不具合調査などで、特定の Firebase Remote Config の値をいつ、どのように変更したか正確に知りたいことがある

そこまで高い頻度で変更するものでもないので、これまではお互い声かけしながらなんとかやっていましたが、比較的安いコストで自動化が可能そうだったのでやってみることにしました。

実際に不具合を起こしている様子

変更はやる前、やった後で関係者に通知する必要がありました

実際やってみた様子

というわけで、実際にやってみた様子をお見せします。

バナーを変更した通知が流れている様子

単に変更された事実だけでなく、

  • 変更内容の diff を表示する
  • JSON を要求するフィールドは JSON schema を利用して検証し、エラーがあれば通知する
  • 画像 URL が入稿されたら画像も表示する

などの機能を盛り込んでいます。

通知するための実装

Cloud Functions for Firebase を使うと Firebase Remote Config が変更されたタイミングで関数を実行することができるので、実装にはこれを利用します。

公式サイト の紹介を引用すると、Cloud Functions for Firebase とは以下のようなものです。

Cloud Functions for Firebase はサーバーレス フレームワークで、Firebase の機能と HTTPS リクエストによってトリガーされたイベントに応じて、バックエンド コードを自動的に実行できます。

Slack 通知の基本的な実装は remote-config-diff (Cloud Functions for Firebase の公式サンプル実装) を参考にしています。

紹介の順番が前後してしまいましたが、実は今回この bot を作成しようと思ったのは、このサンプルコードをたまたま見て意外と簡単にできそうだと思ったことがきっかけでした。

実装にともなっていくつか機能を足したり JSON Diff の整形結果を調整したりしたため、元のコードより少し長くなりましたが、TypeScript のコードは全体で 230 行程度で済みました。

実装のメイン部分は以下のようになっています。

import {WebClient} from "@slack/web-api";
import {onConfigUpdated} from "firebase-functions/v2/remoteConfig";
import {getRemoteConfig} from "firebase-admin/remote-config";
import * as jsonDiff from "json-diff";

// 定数を環境変数や secret として定義しておきます
const slackChannel = defineString("NOTIFY_REMOTE_CONFIG_SLACK_CHANNEL");
const slackApiToken = defineSecret("NOTIFY_REMOTE_CONFIG_SLACK_API_TOKEN");

export const notifyRemoteConfigDiff = onConfigUpdated({secrets: [slackApiToken]}, async (event) => {
  try {
    // 最新と一つ前のバージョン番号を取得します
    const currentVersion = event.data.versionNumber;
    const previousVersion = currentVersion - 1;

    // バージョン番号から RemoteConfigTemplate オブジェクトを取り出します
    const remoteConfig = getRemoteConfig(app);
    const [previousTemplate, currentTemplate] = await Promise.all([
      remoteConfig.getTemplateAtVersion(previousVersion),
      remoteConfig.getTemplateAtVersion(currentVersion),
    ]);

    // 変更をした人のメールアドレスを取り出します
    const author = currentTemplate.version?.updateUser?.email ?? "unknown user";

    // RemoteConfigTemplate を受け取って、特定のキーの値が書式通りになっているかを検証し、エラーメッセージを返します
    // 実装は省略しますが、JSON 型のフィールドに対して別途定義した JSON schema と照らし合わせています
    const errorMessages = validateTemplate(currentTemplate);

    // diff ヘッダを出力します
    const header = `--- version ${previousVersion}\n+++ version ${currentVersion}\n`;

    // JSON に変換して diff を取ります
    const diff = header + jsonDiff.diffString(
      // RemoteConfigTemplate オブジェクトをプレーンな JSON 互換オブジェクトに変換します (実装は省略)
      normalizeFields(previousTemplate),
      normalizeFields(currentTemplate),
    );
    const slack = new WebClient(slackApiToken.value());
    slack.chat.postMessage({
      channel: slackChannel.value(),
      blocks: {...} // メッセージを組み立てます
    });
  } catch (error) {
    logger.error(error);
  }
});

Cloud Functions for Firebase が提供する基本的な機能を使った短いコードですが、意外と簡単に実装できることがわかるのではないでしょうか。

まとめ

Firebase Remote Config の変更通知についてご紹介しました。

当初はやや趣味的な改善で、あまり検討せずにエイヤッと導入してみましたが、意外と Firebase Remote Config の変更が他のチームからも頻度高くされていることがわかったり、エラーが起こっている原因を調べやすくなったり、わずかにチーム全体の QOL が上がっているのを感じます。

スタディサプリの開発チームはこういった 斧を研ぐ 時間を大事にする伝統があり、プロダクトに直接入らない部分についても各自が積極的に改善を進めています。

製品そのものも、またそれを開発する過程自体も、こうやって少しずつ改善を重ねてより良いものを生み出していきたいと思っています。

このような文化で開発したい方は、ぜひ採用ページもご覧ください。