💭

通知の言語を Web と App で分けた話

こんにちは!影山です。

先日、プッシュ通知と、デスクトップ通知/Eメール/お知らせ(KANNAの機能) で見える言語を、App と Web で分ける機能がリリースされました。

  • プッシュ通知 —> デバイスで設定されている言語
  • デスクトップ通知/Eメール/お知らせ(KANNAの機能) —> Web 版 KANNA で設定した言語

元々の実装は、全ての通知は App の言語を見るように実装されていましたが、Web 版 KANNA では言語設定のページを設けているため、Web 版はその言語で通知が飛ぶよう処理を変更しました。

Web版KANNAでの言語設定の切り替えページ

Web版KANNAでの言語設定の切り替え後

仕組み

ざっとした流れで言うと、

1:ユーザーから、通知に関わる何らかのアクション

2:API側で、アクションに応じたテキストを生成

3:2のとき、API側で持っている辞書で App と Web 用のテキストも作る

4:Firestore に3のデータを送信

5:通知が FCM 経由で実行

のようになっています。

通知実行に使われる機能は、KANNAを支える通知基盤を支えるFirestoreの話 の通り、 Firestore, Cloud Functions を使用しています。

API側

Firestore に渡ってくるテキストは KANNA の API (Rails) 経由で来ます。

{
   "message" => {
      "title" => {
         "firebaseUidXXXX" => {
            "web": "案件の状態が 契約前 に変更されました",
            "app=>""案件の状態が 契約前 に変更されました"
         },
         "firebaseUidYYYY" => {
            "web": "Project status has been changed to Pre-contract",
            "app": "案件の状態が 契約前 に変更されました"
         },
         "firebaseUidZZZZ" => {
            "web=>""案件の状態が 契約前 に変更されました",
            "app=>""案件の状態が 契約前 に変更されました"
         },
         "firebaseUidSSSS" => {
            "web": "Project status has been changed to Pre-contract",
            "app": "案件の状態が 契約前 に変更されました"
         }
      }
	他の処理...

API 側の時点で、 firebase_uid ごとに App と Web で分けてテキストを渡すことで、タイ語やスペイン語など、 KANNA で扱える言語の数が増えても同じ形で対応することができます。

各ユーザーが設定した言語でテキスト生成されているかどうかは、API 側に一任させました。

RSpec では言語切り替えできているかテストします。

expect(message[:title]).to eq ({
  user2.firebase_uid => {
    web:"#{user1.full_name}(#{user1.belonged_company.name})has added reports.:#{cm_project.title}",
    app:"#{user1.full_name}(#{user1.belonged_company.name})が報告を追加しました:#{cm_project.title}"
  },
  user3.firebase_uid => {
    web:"#{user1.full_name}(#{user1.belonged_company.name})が報告を追加しました:#{cm_project.title}",
    app:"#{user1.full_name}(#{user1.belonged_company.name})has added reports.:#{cm_project.title}"
  }}
)

上記のおかげで、Firestore 側では App と Web で渡すテキストを分ける実装のみを任せることができました。テストが pass したのに言語が切り替わっていない場合、 Firestore 側に非があることが分かるため、原因の切り分けがスムーズだったことは幸いしました。

この部分の実装に関しては、前実装者に感謝しつつ、チームで相談しつつ、で進められたことがとても良かったです。

Firestore側

Firestore 側では渡ってきたデータを整形して、

notifications コレクション > 
		notification ドキュメント(firebase_uid群) >
				notification フィールド

notification フィールド内に、

{
  titleLocalized: {
    app: "xxx",
    web: "yyy"
  }
}

といった形で、firebase_uid に対応した App と Web のデータを送ります。

フィールドに入っている App と Web の値は、再度 Firebase 側で値を取ってきます。

簡略しますが、最終的には下記のような形で push通知 / デスクトップ通知 / Eメール に FCM経由で配信される形となります。

export class NotificationQueueItem {
	...いろんな処理...
	toFcmMessage = (): TokenMessage => {
    return {
      token: this.deviceToken,
      notification: {
        title: this.buildTitle(),
        body: this.buildBody(),
      },
			...
		}
	}
}
// push通知に送る場合の処理
const sendPushNotification = async (item: NotificationQueueItem): Promise<PushResult | undefined> => {
	const fcmMessage = item.toFcmMessage()	
	try {
		const batchResponse = await sendPushNotifications([fcmMessage])
		const response = batchResponse.responses[0]
		return {
			...,
		}
	} catch (e) {
		...,
	}
}

まとめ

API 側でテキストを生成し、Firestore 側で通知処理を行うことで責務を分けられていることはデバッグのしやすさにも繋がりました。

初めて Firestore 周りを触ったので実装に時間がかかりましたが、その分成長できたと思っています。

また、今回の記事内容には挙げませんでしたが、KANNA では Firestore 周りを一部 SDK化して App/Web/Firebaseで 共有化しており、コレが DDD で書かれています(ここのキャッチアップにも苦労しましたが、その話は DDD を理解したいつの日かに書こうと思います)。

アルダグラム Tech Blog

Discussion