canpaku / study_record

日々の学習履歴
0 stars 0 forks source link

Meteor同士の通信 #17

Open canpaku opened 6 years ago

canpaku commented 6 years ago

ドキュメントより.

Meteor DDP another みたいなかんじで検索したらでてきた.

canpaku commented 6 years ago

和訳

ますます多くの開発者が発見しているように、MeteorはWebアプリケーションを作成するための非常に強力で機能豊富なプラットフォームです。 Meteorは、ユーザー認証やクライアントとサーバー上のデータの同期化など、プロダクションアプリを展開する際の伝統的な不満の多くを取り除くことで、アイデアからプロトタイプまで完全に機能する製品への移行を大幅に簡単にします。 しかし、開発者はこのような力に惑わされ、Pub / Subフレームワークの最も基本的なコンポーネントの1つを完全に理解することなく、複雑な複数ページのアプリケーションを作成する危険性があります。 これは確かに私の個人的な歴史についての正確な記述であり、私が最近説明したように、メテオと私が最近入れた状況です。

Meteor.publish

私がすべき最初の発言は、Meteorの低レベルの公開APIの正式なデモンストレーションは存在するだけでなく、公式の文書にはじめて表示されることです。 私はこれが問題の一部であると仮定することができます - カウントごとの例は比較的微妙で、DDP(Meteorがクライアントとサーバーの通信のために特別に開発したプロトコル)の理解から利益を得ます。 下の輝くものに行くために彼らの熱意の中で彼らの最初の訪問でそれ以上。 それは、間違いです。少なくとも、このトピックの理解を深めて解決できるSOに関する最近の質問の数で判断してください。

1つのメソッド内に2つのパブリケーションパターン

Meteorに精通している人なら誰でも、利用可能なパブリケーションパターンの最初のものを知っています。パブリッシュ関数は使い慣れたコレクションAPIを使ってコレクションカーソルを返します。 DDPについて知っている人は、カーソルオブジェクト自体がこのプロトコルを介して実際には通信できないことに気付くでしょう。したがって、カーソルを返すことは、実際にはアプリケーションデザイナーがクライアント上で利用できるようにしたい文書(現在と将来)を記述する方法です。 Meteorの内部は、DDPを介してこれらのオブジェクトの実際の送信を処理し、変更のためにカーソルを観察し続け、クライアント側でonReadyコールバックおよびreadyメソッドによって使用される初期送信後にreadyメッセージを送信し続けます。鉄のルータのwaitメソッドとwaitOnフックでさらに利用されています。

ここでは、10秒ごとに[0,1000]の範囲のランダムな整数を入力したテストコレクションについて説明します。使い慣れたカーソルベースのpub-subパターンの例もあります。

TestData = new Meteor.Collection('testdata');

if (Meteor.isServer) {
  Meteor.publish('cursorPub', function(filter) {
    return TestData.find(filter || {});
  });

  Meteor.startup(function () {
    Meteor.setInterval(function() {
      TestData.insert({number: Math.floor(Math.random() * 1000)});
    }, 10000);
  });
}

if (Meteor.isClient) {
  Session.set('filter', {});

  Deps.autorun(function(c) {
    mySub = Meteor.subscribe('cursorPub', Session.get('filter'));
  });
}

クライアント側のサブスクリプションはDeps.autorunブロック内に含まれ、リアクティブなSession変数に依存していることに注意してください。 つまり、セッション変数の値を変更するだけでサブスクリプションフィルタを変更することができ、Meteorは再サブスクリプションを管理するのに十分なほど賢明です(フィルタが実際に変更されていない場合は何もしません)。

Chrome Dev Toolsのネットワークインスペクタで[Websockets]フィルタを使用すると、このpub / subの例をクライアントが送受信するDDPメッセージに変換する方法を正確に確認できます。

ボトムアップから読んだり、meteor internalであるmeteor_autoupdate_clientVersionsサブスクリプションに関するメッセージを無視すると、次のことが分かります。

クライアントからサーバーに送信された接続メッセージ。 クライアントから来て、サブスクリプションIDが添付されているcursorPubパブリケーションのサブスクライブメッセージ。 サーバーから返された接続メッセージ。 testdataのコレクションフィールドとともに2つの追加されたメッセージが返され、クライアントがこれらのドキュメントの格納先と(この場合はかなり限定された)ドキュメントの内容を知ることができます。 このサブスクリプションの初期データがすべて送信されたことを示す、サーバーによって送信された準備完了メッセージ。サブフィールドは、ステップ(2)でクライアントによって送信されたサブスクライブメッセージと同じIDを持つことに注意してください。 サーバー上のsetIntervalブロックとしてさらに追加されたメッセージが再度実行され、新しいドキュメントがコレクションに追加されます。 Meteorは、Meteor.publishブロックによって返されたカーソルを自動的に観察し、サブスクライブするクライアントに変更を送信します。このメッセージのタイムスタンプは他のタイムスタンプよりも数秒遅れており、接続が確立されて最初のデータが同期された後に追加されたドキュメントであることを確認します。

分散データプロトコル

Pub / Subモデルを使用する第2の方法は、実際には、特定の接続およびサブスクリプション要求に関連して、サーバー側からのこのDDPフローへの正確なAPIです。 つまり、指定されたパブリケーション・ファンクションは、その名前の受信サブスクリプションごとに1回実行されますが、カーソルを戻してMeteorに任せて、必要なDDPメッセージを生成するのではなく、カスタマイズされたDDPメッセージを アプリケーションの要件 上記の例とまったく同じ例があります:

Meteor.publish('ddpPub', function(filter) {
  var self = this;

  var subHandle = TestData.find(filter || {}).observeChanges({
    added: function (id, fields) {
      self.added("testdata", id, fields);
    },
    changed: function(id, fields) {
      self.changed("testdata", id, fields);
    },
    removed: function (id) {
      self.removed("testdata", id);
    }
  });

  self.ready();

  self.onStop(function () {
    subHandle.stop();
  });
});

では、ここでは正確に何をしていますか?

observeChangesに含まれる関数の中でそれを使用する必要があるので、publish関数のコンテキストへの参照をselfに格納します。ここでは、この値は異なります。 Observerを設定してTestDataコレクションを監視する(適切にフィルタリングされた)。この単純な例では、パブリッシュ関数に指示して適切なDDPメッセージを正確に送信するように指示することによって、サーバー上の返されたドキュメントセット(ドキュメントが追加、変更、または削除されたとき)をそのまま購読クライアントに渡します。同じ詳細をクライアントに伝えます。これは、自己が追加し、自己変更し、自己削除した呼び出しがやっていることです。 これらの関数は、その特定のサブスクライバに対して即座に同期的に実行されるため、既存のすべてのドキュメントは、追加の処理が行われる前に追加されたフックを起動します。 したがって、self.ready()に達する頃には、既存のすべてのドキュメントがself.added呼び出しですでに送信されていることを確信でき、クライアントにサブスクリプションの使用準備ができていることを伝えることは安全です。何らかの段階でこの呼び出しを行うことは非常に重要です。そうでなければ、サブスクリプションのreadyメソッドにフックするものは決して起動されません。 最後に、サブスクリプションがクローズされた時点(onStopコールバックが発生した時点)にオブザーバを停止させる必要があります。これを行わないと、サーバーは変更を送信するサブスクライバーがなくても、再始動するまで文書セットを継続して観察し、クライアントが接続して再接続すると、サーバーのリソース使用率は急激に増加します。これは良い結果にはならないでしょう! Chrome Dev Toolsで結果を調べると、結果は変わっていません。

むしろより有用なもの

この段階では、かなり多くのコードを使用して、まったく同じ一連のメッセージを再作成しただけなので、これが何であるかを尋ねることは合理的です。 しかし、現在、クライアントとサーバーのデータベース間の会話を仲介する方法があるため、膨大な電力が得られます。 これは、Meteorの文書内で提供されているカウントごとの例では非常に巧みに利用されていますが、少し違うタックをとるつもりです。

既存のドキュメントと新しい追加

Stack Overflowなどで定期的に尋ねられる1つの質問は、クライアントがローカルコレクションを完全に同期させてから、クライアントがそれを操作しようとする前にどのように確認できるかということです。大部分のケースはready()メソッドを使い分けることで解決できると主張します。このメソッドは反応し、上記のDDP準備完了メッセージによって起動されます。しかし、私はウェブソケットの専門家ではなく、修正を要請していますが、私は、メッセージがサーバーから送信される順序と、受信した順序とは必ずしも同じではない場合があることは確かです少なくともある程度の待ち時間がある場合は、クライアント。これは、準備完了メッセージが1つ以上の文書の前に到着することを意味します。 Meteorの組み込みの反応性は、不足している文書が到着するとすぐにすべてを再レンダリングし、クライアントはほとんど気付かないため、これは問題ありません。しかし、その結果でも容認できないものがあればどうでしょうか?

パターン1:ドキュメントカウンター

元の文書セットがそのまま到着したことを確認する1つの方法は、レディコール前に追加された文書の数を追加の文書そのものとして送ることです。 これは、実際に通信に関心のあるデータセットからフィルタリングする必要がないように、別のコレクションを使用して行うのが最適です。

CollectionCount = new Meteor.Collection('collectioncount'); // NOTE THAT THIS ONLY NEEDS TO BE DECLARED ON THE CLIENT

Meteor.publish('ddpPub', function(filter) {  // WHILST THIS IS OBVIOUSLY ON THE SERVER
  var self = this,
      ready = false,
      count = 0;

  var subHandle = TestData.find(filter || {}).observeChanges({
    added: function (id, fields) {
      if (!ready)
        count ++;
      self.added("testdata", id, fields);
    },
    changed: function(id, fields) {
      self.changed("testdata", id, fields);
    },
    removed: function (id) {
      self.removed("testdata", id);
    }
  });

  self.added("collectioncount", Random.id(), {Collection: "testdata", Count: count});

  self.ready();
  ready = true;

  self.onStop(function () {
    subHandle.stop();
  });
});

このパブリッシュ関数は、既存のセット内のドキュメントの数を含むオブジェクトで、CollectionCountコレクション(クライアント上で作成する必要がある)も設定します。 次に、TestData.find()。count()がCollectionCount.findOne({Collection: "testdata"})と等しくなるまで、クライアント上のミッションクリティカルなロジックを遅延させることができます。 publish関数が対応するメッセージをまだ送信していないので、CollectionCount.findOne({Collection: "testdata"})が何も返さない期間を考慮に入れるためには、実際のロジックを少し複雑にする必要があることに注意してください。

パターン2:追加フィールド

同様のシナリオでは、購読するときにコレクション内の文書の正確な数を知る必要はありませんが、その文書がどれであるかを知る必要があります。 これは次のように解決できます。

Meteor.publish('ddpPub', function(filter) {
  var self = this,
      ready = false;

  var subHandle = TestData.find(filter || {}).observeChanges({
    added: function (id, fields) {
      if (!ready)
        fields.existing = true;
      self.added("testdata", id, fields);
    },
    changed: function(id, fields) {
      self.changed("testdata", id, fields);
    },
    removed: function (id) {
      self.removed("testdata", id);
    }
  });

  self.ready();
  ready = true;

  self.onStop(function () {
    subHandle.stop();
  });
});

準備完了メッセージを受け取った直後にクライアント上のコレクションに問い合わせると、TestData.find()。countがTestData.find({existing:true})。count()に等しいことがわかります。 しかし、追加の文書がクライアント側に追加されると、これらの文書は既存のプロパティをもはや持たなくなり、これらがグローバルな観点から真に新しく追加された文書であることを識別できるようになります。

上記の2つの例は、データが急速に生成されているという問題を解決するために組み合わせることができ、クライアントは適切な数のドキュメントを保持するだけでなく、既存のセットのドキュメントと同じであることを確認する必要がありました。 代わりに、いくつかのユースケースでは、既存のドキュメントをまったく送信したくないかもしれません。この場合、!readyがtrueに評価された場合、self.addedコールを実行しないでください。

結論

うまくいけば、これは、低レベルのPublications APIの能力と柔軟性を明らかにしてくれました。これは、高レベルのカーソルベースAPIの利便性にあまりにも慣れ親しんできたMeteor愛好家にはよく無視されると思います。 私は選択的な更新、定期的な削除、無数の余分なデータを含むレコードの拡張などを含む数百もの面白い、より精巧なユースケースがあると確信しています。

購読に関する注意

最後に、先に指摘したように、フィルターを修正したときに自動的に再サブスクリプションできるように、サブスクリプションをクライアント側のDeps.autorunブロックに入れました。 これは、反応性の観点から他の利点を提供する:

canpaku commented 6 years ago
canpaku commented 6 years ago