
NOT A HOTELアプリのチャット基盤のリプレース計画
NOT A HOTELアプリのチャット基盤のリプレース計画
この記事は、NOT A HOTELのソフトウェア開発連載「Inside the SOFTWARE TEAM」です。
はじめに
NOT A HOTELでのChat/Conciergeチームでソフトウェアエンジニアをしている光瀬です。
本記事は、NOT A HOTELのソフトウェア開発連載「Inside the SOFTWARE TEAM」の一つとして、チャット機能の基盤リプレースを「Chat/Conciergeチームの視点」から振り返るものです。
別の記事で、Androidエンジニアの松尾が「NOT A HOTELアプリのチャット機能リプレースの裏側」と題して、Android / iOSアプリ側のチャットリプレースについて詳しく紹介しました。
今回はもう少し視点を引いて、
なぜチャット基盤自体を移行することにしたのか
どんな全体計画で移行を進めているのか
バックエンド・チャット基盤では何をしているのか
といった話を書いていきます。
外部のチャットサービス+SDK に強く依存した構成から、NOT A HOTELがこれから実現したい体験にフィットするよう、チャット基盤を少しずつ内製化していくプロセスを紹介します。
1. チャット機能の位置づけ
NOT A HOTELアプリには、コンシェルジュとのチャット機能があります。このチャット機能は以下のように、お客様からの滞在に関する質問やリクエスト、ホテル運営からの案内など、NOT A HOTELでの滞在時のメインの連絡手段として使われる機能です。
到着時間やチェックインの相談
現地でのレストラン・アクティビティの相談
備品やアメニティに関する問い合わせ
スタッフからの事前連絡や当日の案内、など
そのため、チャットをリプレースするにあたっては、まず「これだけは守りたい」という前提があります。
大きな UI 変更やリプレースで戸惑いや使い勝手の悪化を生み出さないこと
移行に伴う障害でチャットが止まるような状況を起こさないこと
今回は、この前提を守りながら、今後さらに新しい体験を積み上げていくための「より柔軟な土台づくり」という位置づけでした。
2. 現行チャット機能の課題
これまでのチャット機能は、外部チャットサービスと、そのモバイルSDK(UI付き)を活用して提供していました。
アプリ側の記事でも触れていましたが、フロントエンド観点では、SDKのUIに強く結合していることでデザインの自由度に制約があり、NOT A HOTELらしい体験をより磨き込む余白を活かしきれない場面がありました。
一方で、バックエンド含め、Chat/Conciergeチームとしては以下のような課題がありました。
基本的に、チャット関連のデータが外部チャットサービスにあり、NOT A HOTELにあるデータ(予約情報やオーナー情報など)と組み合わせたクエリを実行しづらい。こうした取り回しでは、より軽快なチャット体験を作る余地を十分に発揮できない。
チャットに登場するデータ構造や概念、イベントモデルが外部サービスの仕様に引きずられる。これにより、今後取り組みたい体験の改善の自由度が狭まる。
例えば、オーナーアプリでは、「現在滞在中の予約」と、その予約に紐づく未読メッセージ数や最新のメッセージを一度に表示したい場面があります。予約に紐づく会話一覧をクエリしたあと、各会話の最新メッセージや未読件数を外部のチャットサービスに個別に取りにいくと、いわゆるN+1クエリの状態になります。こうした問題は、チャットデータの一部を自前DBに部分的にレプリケーションすることで対処可能です。
一方で、「メッセージ」「会話/スレッド」「既読」「イベント」といった概念の設計が外部サービス基準のままです。将来的に、チャットを通してお客様とコミュニケーションをAI・コンシェルジュ・運営スタッフがより一体的に担う体験や、裏側のチケット管理システムとシームレスにつながる新しいチャット体験を作ろうとしたときに、外部サービスが提供する枠組みに沿ったままでは、新しく実現したい体験の自由度が限定されます。そのため、新しいチャット体験を柔軟に描いていくうえで、仕様やデータ構造の自由度を高めておくことが重要になります。
3. チャット基盤リプレースの全体方針
チャット機能は、NOT A HOTELでの滞在時のメインの連絡手段です。
その位置づけと、ここまでに挙げた課題を踏まえ、「いますぐ全部を自前化する」のではなく、段階的に自前の基盤へ寄せていく方針をとることにしました。ここでは、その全体方針について紹介します。
ざっくり分けると、フェーズは次のようになります。
Phase 1:外部チャットSDKの廃止・内製実装への置き換え
Phase 2:チャットデータの内製基盤への移行と、それに伴うパフォーマンス改善
Phase 3:NOT A HOTELの滞在を支えるチャット体験の継続的な向上


このフェーズ分けにあたっては、特に次の点に配慮しました。
移行はトラブルが起きやすく、チャットは障害時の影響も大きい機能です。そのため、何かあれば従来のチャット実装にフォールバックできるようにしておき、リスクを下げる。
関わるチームがなるべく独立してリプレイスを進められるようにする。フロントエンド / バックエンド / CS など関与するチームが広く、開発スケジュールの調整が難しいため、他プロジェクトと並行しつつ進められる構成にする。
フロントエンド実装のうち、Web版だけはしばらくは外部SDKを使用したままにしておき、移行先の内製チャットに問題があった場合には、Android / iOSアプリからWeb版チャットを使用できるようにしています。
4. 外部チャットSDKの廃止・内製実装への置き換え
最初のフェーズでは、まず 外部チャットSDKに依存したフロントエンド構成をやめることをゴールにしました。UIをSDKから剥がしつつ、SDKが担っていたメッセージやタイピングインジケーターのリアルタイム配信や、それに伴う閲覧・送信権限チェックを内製チャット基盤に置き換えました。
バックエンド側
バックエンド側では以下のような対応を行いました。モバイルアプリ側での対応は「NOT A HOTELアプリのチャット機能リプレースの裏側」をご覧ください。
外部チャットサービスのAPIを 薄い内製APIでラップ。ここに作業の境界をおくことで、他チームのリソースをなるべくとらずに後続のフェーズで段階的に内部の実装を置き換えていけるようにしました。この時点では、チャットデータのオーソリティは外部のチャットサービスであり、外部サービスからWebhook経由で内製サービスにデータを取り込んでいます。当面は内製サービスのDBはread用のキャッシュ的に扱い、結果整合を前提としています。
メッセージ受信やタイピングインジケーターの表示・非表示などのリアルタイムイベントは、会話ごとに購読チャンネルを分けた WebSocket 経由で配信するようにしました。モバイルデバイスでは再接続が発生しやすく、接続が切れている間に受け取れなかったイベントによる変更は、HTTP API経由で最新の状態を取得できるようにしています。
リアルタイム性とセキュリティのバランス
チャット機能では、滞在予約に紐づいた会話(チャットルーム)を開いている間、メッセージや「相手が入力中」といった状態の変化をリアルタイムに届けるために、WebSocket を使っています。クライアント側で同時に開ける予約チャットは最大ひとつだけで、その間にその会話で発生したイベントは、すべてひとつの WebSocket 接続経由で配信されるイメージです。
こうしたチャットにおけるリアルタイム通信ではリアルタイム性とセキュリティ / プライバシーのバランスが重要だと考えています。構成自体はオーソドックスですが、リアルタイム通信で「何をどこまで流すか」 を意識して設計しました。
リアルタイム通信は push 型である以上、一度サブスクライブされるとサーバーから情報が流れ続けます。セキュリティインシデントはもちろん起こさない前提で設計・運用していますが、この性質上、万が一問題が起きた場合には影響範囲が一気に広がりやすいレイヤーでもあります。
そこで内製基盤では、リアルタイムの配信レイヤーはあくまで「イベント通知専用」と割り切り、ここでは会話IDやイベント種別などの最小限の情報だけを流すようにしています。メッセージ本文や個人情報を含む詳細なデータは、アプリ内で利用する他のAPIと同様の通常の認証・認可を必ず通す構成にしています。

通信の例。WebSocket側でもセキュリティを担保していれば大丈夫との見方もできるが、チャットにおけるユーザーの行動やその内容は、サービスの性質上特に注意して守りたいものの一つ。そのためパフォーマンスとのバランスも見ながら安全性がより高い設計に寄せている。
こうすることで、本来決して起こってはならないことですが、万が一にでも、リアルタイム配信レイヤーで設定ミスや脆弱性が生じた場合に、漏れうる情報の範囲を「どこかでなにかが起きた」程度のぼやけたものに限定できます。
具体的には、次のような分離をしています。
クライアントは WebSocket でサーバーに接続し、
「新しいメッセージが届いた」「相手が入力中」などのイベントを push で受け取り、
実際のメッセージ本文やメタデータは、別の HTTP API(アプリ内の他の通信と同様に認証・認可を通したもの)で取得する
ライブ配信のコメント欄のように秒間何千件もイベントが流れるケースだと、このような構成は現実的ではないかもしれません。しかし、NOT A HOTELのチャットは「滞在時のコンシェルジュとのやりとり」が中心であり、トラフィック規模もそれに見合ったものです。
この前提であれば、リアルタイム通知を軽く保ちつつ、本文は都度APIで取りにいく という構成が、パフォーマンスとセキュリティのバランスがよいと判断しました。
5. これからやりたいこと
Phase 1 の完了によって、
モバイルアプリ側は外部 SDK依存から脱却し、自前実装のチャット画面に移行
バックエンド側も、上記に必要な最低限のAPIとデータを自前で持ち始めた
というところまでは到達できました。
ただし、ここまでがようやく「スタートライン」です。ここから先、Phase 2 / 3 の内製基盤化と、チャット体験そのものの改善に踏み込んでいきたいと考えています。
内製基盤へのデータ移行とパフォーマンス改善
基盤整備を進める中でまずは以下のようなわかりやすい改善につなげていきたいと思っています。
会話一覧・未読件数・最新メッセージといった、画面表示のたびに参照される情報を自前DBだけで効率よく取得できる状態にする。
チャット上のメッセージと、予約変更・決済・スタッフ側の操作ログなどの運営上のイベントを、ひとつのタイムラインとしてクエリしやすくし、対応の品質向上につなげる。
データを完全移行するうえでは、外部チャットサービスと内製基盤の両方のデータストアへ書き込むdual writeな期間を挟むことになりうるので、そこもまた一つのハードルになるポイントかと思っています。書き込み時に片方が失敗する・書き込み順序がずれるなどの問題が起きた場合にどのように対処するのかが課題です。この点については、別途記事にできたらと考えています。
チャットとしてのさまざまな体験改善
チャットを内製化するゴールは、「自前にすること」そのものではなく、NOT A HOTELの滞在体験にフィットしたコミュニケーションを継続的に改善できるようにすることです。
今後の方向性として、Chat/Conciergeチームでは例えば次のようなことを考えています。
AI Concierge「Kevin」の大きなアップデート
テキストだけにとどまらない、インタラクティブなチャットへ
NOT A HOTEL のチャットにはAI Conciergeである「Kevin」がいます。こちらも大きな改善を進めている最中なので、チャットとも強い連携を実現していきたいです。今回のチャット基盤リプレースは、こうしたチャットに登場する概念の再設計が必要な可能性のあるアップデートを、無理なく積み重ねていくための「足場づくり」でもあります。
6. おわりに・採用のご案内
チャットのような重要な機能を、止めずに、しかし確実に新しい基盤へ移行していくのは、規模の大小問わずやはりハードルがある仕事ですが、それと同時に面白さもあります:
外部サービスとどう付き合いながら、どこから内製化していくか
どうやってフロントエンド / バックエンド / CS などチーム横断で安全にリプレースしていくか
その先に、どんな体験を乗せていけるか・このAI時代にチャット機能をどう落とし込んでいくか
こういったテーマに興味がある方とは、ぜひ一緒に仕事ができればと思っています。
現在、NOT A HOTELのSoftwareチームでは採用強化中です。カジュアル面談も受け付けておりますので、お気軽にご連絡ください。 https://herp.careers/v1/notahotelinc/requisition-groups/8940c119-d9dc-4ab4-b9c0-960a16d67479