カスタム XMPP ソリューションへの移行の成功
公開: 2019-04-08英国を拠点とするメッセージング ヘルスケア ソリューションであるクライアントである Forward Health のために、サードパーティ チャットからカスタム XMPP ベースのメッセージング ソリューションに移行する際に直面した課題について説明します。 この記事では、移行の理由、私たちの期待と実装の現実、および追加機能を構築する際の課題について説明します。
私たちが始めた場所
クライアントである Forward Health は、チャット機能を含む、英国の医療従事者向けのモバイル通信アプリケーションを構築したいと考えていました。 スタートアップとして、彼らは動作中の製品をすばやく見せたいと考えていました。 同時に、メッセージングは信頼性が高く、堅牢で、患者の機密データを安全に送信できる必要がありました。 これを実現するために、チャット機能に利用可能なサードパーティ ソリューションの 1 つを使用することにしました。
チャット機能は、特にヘルスケア業界をサポートすることを目的としている場合、簡単なことではありません。 アプリが成長するにつれて、サードパーティが取り組みたがらない、より多くのエッジケースとライブラリ側のバグに遭遇しました。 さらに、Forward Health は、サードパーティ ライブラリでサポートされていない新機能を追加したいと考えていました。 次のステップは、カスタム ソリューションへの切り替えでした。
その時、私たちはMongooseIMを使い始めました。 MIM は、確立された XMPP プロトコルに基づくオープン ソース ソリューションです。 外部企業の Erlang Solutions Limited と協力して、バックエンドをセットアップし、カスタム ソリューションの実装をサポートしました。
最初は、メッセージングに関するすべてが異なっているように見えました。 以前は、SDK とその REST API によってすべてのニーズが満たされていました。 さて、MongooseIM を使用して、XMPP の性質を理解し、独自の SDK を実装するのに時間がかかりました。 「最低限の」XMPP サーバーは、クライアント間でスタンザ (XML メッセージ) をリアルタイムで渡すだけであることが判明しました。 スタンザには、通常のチャット メッセージ、プレゼンス、リクエスト、レスポンスなど、さまざまな種類があります。 サーバーにさまざまなモジュールを追加して、たとえばメッセージを保存し、クライアントがメッセージを照会できるようにすることができます。
クライアント側 (Android、iOS) には、低レベルの SDK がいくつかありました。 残念ながら、それらは MongooseIM と XEP と呼ばれるプラグイン可能なモジュールの一部との通信を可能にするレイヤーとしてのみ機能していました (XMPP 拡張プロトコルは、とりわけ、すべてのメッセージのプッシュ通知を送信する役割を果たします)。 メッセージの処理、メッセージの保存、およびクエリのアーキテクチャ全体を、私たちのチームが実装する必要がありました。
助けになったのは、以前使用していたサードパーティのライブラリでした。 非常によく考え抜かれた API を備えていたので、私たちのソリューションも同様の方法で機能するようにしました。 XMPP 固有のコードを内部 SDK に分離し、以前のソリューションのインターフェイスに対応するインターフェイスを使用しました。 これにより、移行後にアプリケーション コードにわずかな変更しか加えられませんでした。
MongooseIM の実装中に、標準だと思っていた要素が XEP でさえ利用できなかったことに何度か驚かされました。
XMPP ベースのチャットの主要機能の実装
タイムスタンプ
私たちが行ったように、タイムスタンプは「メッセージを受け取ったので、これをタイムスタンプとともに UI に表示する」のように単純なものだと思うかもしれません。 いいえ、それほど簡単ではありません。 デフォルトでは、メッセージ スタンザにはタイムスタンプ フィールドがありません。 私たちのチームにとって幸いなことに、XMPP は簡単に拡張できるプロトコルです。 バックエンドでは、MongooseIM サーバーを通過するすべてのメッセージにタイムスタンプを追加するカスタム機能を実装しました。 次に、受信者はメッセージにタイムスタンプを添付します。
送信者が自分でタイムスタンプを追加できないのはなぜですか? まあ、彼らの電話に正しい時刻が設定されているかどうかはわかりません。
そのための XEP がないのはなぜですか? おそらく、XMPP はリアルタイム プロトコルであるため、送信されたすべてのメッセージは理論的にはすぐに受信されます。
編集: Florian Schmaus が指摘したように、「紛らわしい名前のために簡単に見逃すことができますが、実際には 1 つ存在します: XEP-0203: Delayed Delivery.」 配信が遅れた場合にのみ、メッセージにタイムスタンプを追加します。 それ以外の場合、メッセージは今送信されました。
オフライン メッセージ
両方のユーザーがアプリケーションにログインすると、リアルタイムで互いにメッセージを送信できます。 しかし、そのうちの 1 つがオフラインの場合はどうなるでしょうか。 簡単な答えは、メッセージはバックエンドでバッファリングする必要があるということです。 オフライン メッセージ機能がこの作業を処理し、ユーザーが再度ログインすると、バッファリングされたすべてのスタンザをユーザーに送信します。
しかし、次のような疑問が生じます。
- これらのメッセージはどのくらいの期間バッファリングする必要がありますか?
- 何人ですか?
- 再度ログインした直後に再送信する必要がありますか? しかし、それはクライアントにメッセージをあふれさせますよね?
- ユーザーがログインするだけで、新しいメッセージでチャットに入らない場合はどうなりますか? それらはすべてなくなりますか?
- ユーザーが複数のデバイスにログインしている場合はどうなりますか?
オフライン メッセージ機能は、最初にオンラインに戻ったデバイスにしかメッセージを送信できず、他のすべてのデバイスでそれらのメッセージが失われることが明らかになりました。 この機能を破棄し、メッセージを別の永続的な方法で XMPP バックエンドに保存することにしました。

メッセージ アーカイブ管理 (MAM)
MAM は、メッセージ用のサーバー上のストレージです。 クライアントがログインすると、サーバーにメッセージを問い合わせることができます。 ページごとにクエリを実行したり、日付ごとにクエリを実行したりできます。 柔軟性があります — 特定の ID を持つメッセージの前または後にページを照会して、正確な会話からのメッセージのフィルターを追加することもできます。
しかし、ここに問題があります。 通常のチャット メッセージは、独自の一意の ID を持つ MAM メッセージ内にラップされて保存されます。 ユーザーがストリームでチャット メッセージを受信する場合、MAM ID は含まれていません。 それを取得するには、MAM にクエリを実行する必要があります。
MAM からの取得はネットワーク リクエストであるため、比較的長い時間がかかる場合があります。 ユーザーがチャットに入ったら、すぐにメッセージを見たいと思っています。 したがって、ローカル データベースも必要です。
ユーザーがストリーム内のメッセージ (オンライン メッセージ) を受け取ると、それをローカル データベースに保存し、ユーザーに表示します。 このようにして、リアルタイムですばやく到着するメッセージをユーザーに表示します。
さらに、彼らがチャット画面に入るたびに、その会話のローカル DB に保存されている最新の MAM メッセージまでのすべてのメッセージをダウンロードし、重複を無視してデータベースに入れます。
これが古いメッセージの保存方法です。 また、データベースには、MAM からの最初のメッセージと最後のメッセージの間の特定の会話に関するメッセージの完全なセットがあると確信しています。
MAM からダウンロードされたメッセージを追跡するために、2 つのプロパティを会話エンティティに追加しました。
- データベース内の最新の MAM メッセージの MAM ID
- データベース内の最も古い MAM メッセージの MAM ID
ローカル データベースで粉々になった MAM メッセージのセットを処理することは、非常に問題になります。
さらに、会話ごとにこれら 2 つのプロパティを使用することで、通常のチャット メッセージをデータベースに保存し、ラッパー (MAM メッセージ) を無視することができます。 ユーザーがチャットに入ると、データベースから最新のメッセージを表示し、バックグラウンドで不足しているメッセージを MAM から取得できます。
受信トレイ
すべてのチャットベースのアプリには、チャットのリストを含む画面が必要です。これは、名前、最後のメッセージ、および未読メッセージ数を確認できる場所です。 解決策があるはずです!
実際にはありません… Roster と呼ばれるものがあり、「友達」としてタグ付けされたユーザーのリストを保持できます。 残念ながら、最後のメッセージも未読メッセージ数も添付されていません。 確かに、バックエンドから必要な情報をバラバラに取得できます。 最初はそのようにしたかったのですが、動作が遅く、複雑でした。 そのとき、私たちは Inbox 機能でErlang Solutionsと協力し始めました。これもオープンソースへの道を進んでいます。
ユーザーが XMPP バックエンドに接続すると、アプリはそのユーザーのすべての会話 (1 対 1 チャットとチーム チャットの両方) を含む受信トレイをフェッチします。 それぞれに、最後に添付されたメッセージと未読メッセージの数があります。 アプリケーションは、受信トレイ全体をローカル データベースに保存します。 ユーザーがアプリ内にいて、新しいメッセージが到着すると、受信トレイの状態をローカルで更新します。 そうすれば、アプリは新しいメッセージごとに受信トレイを取得する必要がなくなります。
概要
一部のサードパーティのチャット ソリューションは、高度な抽象化を提供します。 単純なチャット アプリケーションを作成する場合は、これで問題ありません。 Forward アプリに独自の XMPP ベースのソリューションを実装することで、はるかに優れた低レベル アクセスを取得できるようになり、問題の解決がはるかに容易になりました。 確かに時間がかかりましたが、NHS によって承認された安全で簡単な方法で、英国の医師が通信できるようにするためのカスタム機能を提供できることがわかりました。
メッセージングとは、高性能のリアルタイム通信に関するものです。 MIM に切り替えることで、ソリューションのすべての部分を最適化して、速度、信頼性、そして最終的には信頼性を向上させることができました。 現在、コード全体が手元にあるので、簡単に追跡できます。 また、安定化段階を経ており、メッセージングに関連するレポートの数が大幅に減少しています。 ユーザーは、プラットフォームを信頼できることに満足しています。
独自の SDK を設計して作成することは困難な作業でしたが、気に入りました。 これは、サーバーからデータを取得して画面に表示する必要がある単純なアプリケーションとは異なるものでした。 実装中に、以前に使用したサードパーティ ライブラリ API の多くの設計上の選択肢を理解しました。 なんで? 同じ問題に遭遇したからです。