リアルタイムの重いWebソケットベースのWebアプリケーションをアーキテクチャ化する方法は?


17

リアルタイムの単一ページアプリケーションを開発する過程で、ユーザーに最新のデータを提供するためにwebsocketを徐々に採用しています。この段階で、アプリの構造を破壊しすぎていることに気がつき、この現象の解決策を見つけることができませんでした。

詳細に入る前に、ほんの少しのコンテキスト:

  • webappはリアルタイムSPAです。
  • バックエンドはRuby on Railsにあります。リアルタイムイベントはRubyによってRedisキーにプッシュされ、その後、マイクロノードサーバーがそれをプルバックしてSocket.Ioにプッシュします。
  • フロントエンドはAngularJSにあり、Nodeのsocket.ioサーバーに直接接続します。

サーバー側では、リアルタイムになる前に、明確なコントローラー/モデルベースのリソース分離があり、それぞれに処理が関連付けられていました。この古典的なMVCの設計は、ユーザーにWebソケットを介してプッシュを開始した直後に、完全に細断された、または少なくともバイパスされました。これで、すべてのアプリが多かれ少なかれ構造化されたデータを流す単一のパイプができました。そして、私はそれがストレスだと感じます。

フロントエンドでの主な懸念は、ビジネスロジックの重複です。ユーザーがページを読み込むとき、古典的なAJAX呼び出しを介してモデルを読み込む必要があります。しかし、リアルタイムのデータフラッディングも処理する必要があり、クライアントサイドモデルの一貫性を維持するために、クライアントサイドのビジネスロジックの多くを複製しています。

いくつかの調査の後、いくつかの特定のトピックを念頭に置いて、現代のWebアプリのアーキテクチャをどのように設計することができ、どのように設計するべきかについてのアドバイスを提供する良い投稿、記事、書籍などは見つかりません:

  • サーバーからユーザーに送信されるデータの構造化方法
    • 「このリソースは更新されたので、AJAX呼び出しでリロードする必要があります」などのイベントのみを送信するか、更新されたデータをプッシュし、初期AJAX呼び出しでロードされた以前のデータを置き換えますか?
    • 送信されたデータに一貫性のあるスケーラブルなスケルトンを定義する方法は?これはモデル更新メッセージですか、または「blablablablahにエラーがありました」メッセージです
  • バックエンドのどこからでもすべてに関するデータを送信しない方法
  • サーバー側とクライアント側の両方でビジネスロジックの重複を減らす方法

RailsがSPAに最適なオプションであると確信していますか?Railsは素晴らしいですが、モノリスアプリケーションを対象としています...懸念の分離を備えたモジュラーバックエンドが必要な場合があります...ニーズに応じて、リアルタイムSPAの代替Rubyフレームワークを検討します。
ミスト

1
Railsが最良の選択肢であるかどうかはわかりませんが、少なくともバックエンドにあるスタックに非常に満足しています(おそらく、この特定のフレームワークが得意だからです)。ここでの私の懸念は、技術にとらわれない視点でSPAを設計する方法についてです。また、単一のプロジェクトで言語、フレームワーク、ライブラリの数を増やしたくありません。
フィリップデュレックス

リンクされたベンチマークには問題があるかもしれませんが、ActiveRecordの現在の実装の弱点を明らかにしています。私はplezi.ioに偏っているかもしれませんが、ベンチマークの後の結果で指摘されているように、クラスタリングとRedis(テストされていません)の前でさえ、大幅なパフォーマンスの改善を提供します。node.jsよりもパフォーマンスが良いと思います...状況が変わるまで、plezi.ioを使用します。
ミスト

回答:


10

サーバーからユーザーに送信されるデータの構造化方法

メッセージングパターンを使用します。さて、あなたはすでにメッセージングプロトコルを使用していますが、変更をメッセージとして構造化することを意味しています。具体的にはイベントです。サーバー側が変更されると、ビジネスイベントが発生します。シナリオでは、クライアントビューはこれらのイベントに関心があります。イベントには、その変更に関連するすべてのデータが含まれている必要があります(必ずしもすべてのビューデータではありません)。その後、クライアントページは、イベントデータで保持しているビューの部分を更新する必要があります。

たとえば、株価表示を更新していてAAPLが変更された場合、すべての株価を押し下げたり、AAPLに関するすべてのデータ(名前、説明など)を押し下げたりすることは望ましくありません。AAPL、デルタ、および新しい価格のみをプッシュします。クライアントでは、ビューでその株価のみを更新します。

「このリソースは更新されたので、AJAX呼び出しでリロードする必要があります」などのイベントのみを送信するか、更新されたデータをプッシュし、初期AJAX呼び出しでロードされた以前のデータを置き換えますか?

私はどちらとも言えません。イベントを送信している場合は、オブジェクト全体のデータではなく、関連するデータを送信してください。イベントの種類に名前を付けます。(そのイベントに関連するネーミングとデータは、システムの機械的動作の範囲を超えています。これは、ビジネスロジックのモデル化方法と関係があります。)ビューアップデーターは、特定のイベントをそれぞれに変換する方法を知る必要があります正確なビューの変更(つまり、変更されたもののみを更新する)。

送信されたデータに一貫性のあるスケーラブルなスケルトンを定義する方法は?これはモデル更新メッセージですか、または「blablablablahにエラーがありました」メッセージです

これは、他のいくつかの質問に分割して個別に投稿する必要がある、大規模で自由な質問です。

ただし、一般的には、ビジネスの重要な出来事のために、バックエンドシステムでイベントを作成してディスパッチする必要があります。それらは、外部フィードから、またはバックエンド自体のアクティビティから入り込む可能性があります。

バックエンドのどこからでもすべてに関するデータを送信しない方法

パブリッシュ/サブスクライブパターンを使用します。SPAがリアルタイム更新の受信に関心のある新しいページを読み込むと、ページは使用可能なイベントのみをサブスクライブし、それらのイベントが発生したときにビュー更新ロジックを呼び出す必要があります。おそらくpub / subロジックをオンにする必要がありますネットワーク負荷を軽減するサーバー。Websocket pub / sub用のライブラリが存在しますが、Railsエコシステムにどのようなものがあるのか​​わかりません。

サーバー側とクライアント側の両方でビジネスロジックの重複を減らす方法

クライアントとサーバーの両方でビューデータを更新する必要があるようです。私の推測では、リアルタイムクライアントを開始するためのスナップショットを取得するには、サーバー側のビューデータが必要です。2つの言語/プラットフォーム(RubyとJavascript)が関係しているため、ビュー更新ロジックは両方で作成する必要があります。トランスピリング(独自の問題があります)を除けば、それを回避する方法はありません。

技術的ポイント:データ操作(ビューの更新)はビジネスロジックではありません。ユースケースの検証を意味する場合、クライアントの検証は優れたユーザーエクスペリエンスに必要ですが、最終的にはサーバーによって信頼されないため、それは避けられないようです。


ここに、そのようなものがうまく構成されている様子を示します。

クライアントビュー:

  • ビューのスナップショットとビューの最後に見たイベント番号を要求します
    • これにより、クライアントが最初から構築する必要がないように、ビューに事前に入力されます。
    • 簡単にするためにHTTP GETを使用することもできます
  • ビューの最後のイベント番号から開始して、Websocket接続を確立し、特定のイベントにサブスクライブします。
  • Websocket経由でイベントを受信し、イベントタイプ/データに基づいてビューを更新します。

クライアントコマンド:

  • データ変更のリクエスト(HTTP PUT / POST / DELETE)
    • 応答は成功または失敗+エラーのみ
    • (変更によって生成されたイベントは、websocketを経由し、ビューの更新をトリガーします。)

サーバー側は、実際には責任が制限されたいくつかのコンポーネントに分割できます。着信リクエストを処理し、イベントを作成するもの。別の方法では、クライアントのサブスクリプションを管理し、イベントをリッスン(インプロセスなど)し、適切なイベントをサブスクライバーに転送できます。イベントをリッスンし、サーバー側のビューを更新するサードパーティを使用することもできます。これは、サブスクライバがイベントを受信する前に発生することもあります。

私が説明したのは、CQRS + メッセージングの形式であり、あなたが直面している種類の問題に対処するための典型的な戦略です。

イベントソーシングをこの説明に含めませんでした。それがあなたがやりたいものなのか、それとも必然的に必要なのかわからないからです。しかし、それは関連するパターンです。


私はこのトピックに関して多くの進歩を遂げており、あなたが与えた指針はとても役に立ちました。たとえすべてを使用していなくても、多くのアドバイスを使用したため、答えを受け入れました。別の回答で私がたどった道を説明します。
フィリップデュレックス

4

主にバックエンドでの数ヶ月の作業の後、ここでのアドバイスのいくつかを使用して、プラットフォームが直面していた問題に対処することができました。

バックエンドを再考する際の主な目的は、可能な限りCRUDに固執することでした。 多くのルートに散在するすべてのアクション、メッセージ、およびリクエストは、作成、更新、読み取り、または削除されるリソースに再グループ化されました。当たり前のように聞こえますが、これは慎重に適用することは非常に難しい考え方です。

すべてをリソースに整理した後、リアルタイムメッセージをモデルに添付することができました。

  • 作成すると、ホールの新しいリソースを持つメッセージがトリガーされます。
  • 更新は、更新された属性(およびUUID)のみでメッセージをトリガーします。
  • 削除は削除メッセージをトリガーします。

Rest APIでは、すべての作成、更新、削除メソッドがヘッドオンリーレスポンス、成功または失敗を通知するHTTPコード、およびWebsocketにプッシュされる実際のデータを生成します。

フロントエンドでは、各リソースは特定のコンポーネントによって処理され、初期化時にHTTPを介してそれらをロードし、更新をサブスクライブし、状態を長期にわたって維持します。次に、ビューはこれらのコンポーネントにバインドしてリソースを表示し、同じコンポーネントを介してそれらのリソースに対してアクションを実行します。


CQRS +メッセージングとイベントソーシングの読み取りは非常に興味深いものでしたが、私の問題にとってはやや複雑すぎて、集中型データベースへのデータのコミットが高価で贅沢な集中アプリケーションにもっと適していると感じました。しかし、私は間違いなくこのアプローチを心に留めておきます。

この場合、アプリには同時クライアントがほとんどないので、私はデータベースに多くの信頼を寄せています。最も変化するモデルはRedisに保存され、1秒あたり数百の更新を処理すると信じています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.