web-dev-qa-db-ja.com

リアルタイムの重いWebSocketベースのWebアプリケーションを構築する方法は?

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

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

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

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

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

いくつかの調査の後、私はいくつかの特定のトピックを念頭に置いて現代のウェブアプリのアーキテクチャをどのようにして設計できるかについてのアドバイスを与える良い記事、記事、本などを見つけることができません:

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

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

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

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

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

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

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

これは大きくて自由な質問で、他のいくつかの質問に分けて個別に投稿する必要があります。

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

すべてのデータをバックエンドのどこからでも送信しないようにするにはどうすればよいですか?

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

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

クライアントとサーバーの両方でビューデータを更新する必要があるようです。リアルタイムクライアントを起動するためのスナップショットを取得するには、サーバー側のビューデータが必要だと思います。 2つの言語/プラットフォーム(RubyとJavascript)があるため、ビューの更新ロジックは両方で記述する必要があります。トランスパイル(それ自体に問題があります)を除いて、私はそれを回避する方法がわかりません。

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


こういうものがうまく構造化されていると私はどう思うか。

クライアントビュー:

  • ビューのスナップショットとビューの最後に確認されたイベント番号を要求します
    • これにより、ビューが事前に設定されるため、クライアントは最初から構築する必要がありません。
    • 単純化のためにHTTP GETを介している可能性がある
  • ビューの最後のイベント番号から開始して、WebSocket接続を確立し、特定のイベントをサブスクライブします。
  • WebSocketを介してイベントを受信し、イベントタイプ/データに基づいてビューを更新します。

クライアントコマンド:

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

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

私が説明したのは、 [〜#〜] cqrs [〜#〜] + Messaging の形式であり、典型的な戦略ですあなたが直面している問題の種類に対処する。

Event Sourcing をこの説明に含めませんでした。それがあなたが引き受けたいものなのか、それとも必要なのかがわからないためです。しかし、これは関連するパターンです。

10
Kasey Speakman

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

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

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

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

Rest APIでは、すべてのcreate、update、deleteメソッドがヘッドのみの応答を生成し、HTTPコードが成功または失敗を通知し、実際のデータがWebSocketにプッシュされます。

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


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

この場合、アプリには同時クライアントがほとんどないため、私はデータベースに大きく依存しているパーティーに参加しました。最も変化するモデルはRedisに保存され、1秒あたり数百回の更新を処理すると信頼しています。

4
Philippe Durix