パブリケーションとサブスクリプション

コラム 4.5

翻訳の進捗

本章で学ぶこと:

  • どのようにパブリケーションとサブスクリプションが動くのか理解します。
  • デフォルトでのAutopublishパッケージがどんなことをするのか学びます
  • パターンの例をいくつか見ていきます。
  • パブリケーションとサブスクリプションは Meteor において、最も基本的で重要なコンセプトの1つです。 しかし、まだ始めたばかりでは理解することが難しいものです。

    そのため多くの誤解の原因ともなっています。 その誤解の中には Meteorは安全でないといったものやMeteorのアプリは大量のデータを扱えないといたものがあります。

    最初にこうしたコンセプトに困惑してしまう理由の多くは、Meteor がもたらす「マジック」によるものです。 Meteor のマジックは最終的にはとても役立つものですが、裏側で何をしているのかわかりません。(マジックってそういうものですからね) 何か起きているのか理解するために、化けの皮を剥ぎ取りましょう。

    昔々

    しかしはじめに、 Meteor が存在しなかった2011年の古き良き時代を振り返ってみましょう。 例えば、あなたが Rails でアプリを作っていたとしましょう。 ユーザーがサイトにやってくると、クライアントでは(つまり、あなたのブラウザーでは)、リクエストをサーバーにあるアプリに送ります。

    このアプリの最初の仕事はユーザーが見る必要のあるデータが何であるか理解することです。 これは検索結果の12ページであったり、メアリーのユーザープロフィール情報や、ボブの最近の20個のツイートといったものかもしれません。 これは基本的にあなた要求した本を探すために、本屋の店員さんが通路を歩いて見て回ることのように考えることができます。

    正しいデータを選び出したら、アプリの2つ目の仕事はデータを良いもの変換します。 例えば、人間が読むことのできるHTML等です。(APIの場合はJSONに変換します。)

    本屋でたとえると、これは購入した本をラッピングしてバックの中にしまうことです。 これが有名な Model-View-Controller モデルでの"View"の部分です。

    最終的に、アプリは HTML コードを取ってブラウザーへ送信します。 これで Rails で作られたアプリの仕事は終わって、すべてのことが手元から離れました。 そのため、Railsで作られたアプリは次のリクエストが来るまで待っている間にビールを飲んでくつろぐことができます。

    Meteorのやり方

    Meteor がなぜ特別なのか比較して おさらいをしましょう。 これまで見てきたように、Meteorの鍵となるイノベーションはRailsのアプリがサーバーでのみ動いているのに対して、 Meteorアプリはクライアント(ブラウザー)でも動くクライアントサイド(ブラウザで動く)のコンポーネントがる点です。

    データベースの一部分をクライアントにプッシュする
    データベースの一部分をクライアントにプッシュする

    これはまるで本屋の店員さんがあなたの欲しい本を見つけるだけでなく、家までついて来て夜中に読んでくれるようなものです。(ゾッとする話ですけどね。)

    この構造はMeteorをクールなものにしています。本屋のチーフのように、 Meteorがデータベースをどこからでも呼び出すのです。 簡単に言うと、Meteorはあなたのデータベースから一部分を取ってきて、それをクライアントにコピーをします

    これには2つの大きな意味合いがあります。 1つ目は、クライントにHTMLコードを送る代わりに、 Meteorアプリが実際の生データを送って、クライアントが対処するようにするという点。 (data on the wire) 2つ目は、サーバーからの応答を待つことなくデータのアクセスや編集さえ瞬時にできてしまう点です。 (latency compensation)

    パブリッシュ

    アプリのデータベースはプライベートから機密データに至る何万ものドキュメントを入れることができます。 そのため、セキュリティやスケーラビリティの観点からもクライント上のデータベースへすべてをミラーするわけにはいきません。

    どの部分のデータをクライントに送るかMeteorに命令する方法が必要で、パブリケーションを通してこれを実現します。

    Microscopeにもどりましょう。これがデータベースに入っているアプリの投稿のすべてです。:

    データベース内のすべての投稿
    データベース内のすべての投稿

    Microscopeにはそのような機能はないのですが、罵詈雑言にフラグを立てることを想定します。 データベースでは、それらを維持したいですが、ユーザーに提供(すなわちクライアントに送信)すべきではありません。

    私たちの最初のタスクは クライントにどんなデータを送りたいのかMeteorに指示を与えることです。 フラグが立っていない投稿だけをパブリッシュしたいということをMeteorに指示します。

    フラグがついた投稿を除く
    フラグがついた投稿を除く

    これが対応するコードで、サーバー内にあります。:

    // on the server
    Meteor.publish('posts', function() {
      return Posts.find({flagged: false});
    });
    

    こうすることで、クライアントがフラグ付き投稿にアクセス可能な方法がないことが保証されます。 まさにこれがMeteorアプリをセキュアにする方法です: 単にクライアントにアクセスしてほしいデータをパブリッシュするだけです。

    DDP

    基本的に、パブリケーション/サブスクリプション システムについては サーバーサイドのコレクション(ソース)をクライアントサイドのコレクション(ターゲット)に、 データを転送するじょうごのようなものだと考えることができます。

    じょうご上で話しているプロトコルはDDPと呼ばれます。(Distributed Data Protocolの略) DDPについてさらに学習するには、Matt DeBergalis(Meteor発起人の一人)によるリアルタイムカンファレンスか、Cris Matherのスクリーンキャストで、もう少し詳細にこの概念を説明されています。

    サブスクライブ

    フラグの立っていないすべての投稿をクライアントでも閲覧できるようにしたいとしても、 一度に幾千もの投稿をすぐに送ることはできません。

    私たちは、クライアントが任意の特定の瞬間に必要なデータがどの部分かを指定するための方法を必要とし、 それはまさにサブスクリプションの出番です。

    サブスクライブしているすべてのデータはMinimongoによってクライアント上でミラーされます。 MinimongoはMongoDBのクライアントサイド実装です。

    たとえば、私たちはボブ・スミスのプロフィールのページを見ているとしましょう。 そして、彼の投稿だけを表示したいとします。

    クライアント上でボブの投稿だけをミラーするようにサブスクライブする
    クライアント上でボブの投稿だけをミラーするようにサブスクライブする

    最初に、引数をを取るためにパブリケーションを改造します。:

    // on the server
    Meteor.publish('posts', function(author) {
      return Posts.find({flagged: false, author: author});
    });
    

    そして、アプリのクライアント側のコードでそのパブリケーションに サブスクライブする時にそのパラメータを定義します: アプリのクライアントサイドのコード内でパブリケーションにサブスクライブするときに引数を定義します。

    // on the client
    Meteor.subscribe('posts', 'bob-smith');
    

    これがMeteorアプリをスケーラブルなクライアントサイドにする方法です。: すべての利用可能なデータをサブスクライブする代わりに、ちょうど、今必要な部分を選択し、 ピックアップします。このようして、サーバー側のデータベースはどんなに大きくとも関係なく、ブラウザのメモリの過負荷を避けることができます。

    Finding

    ボブの投稿は多数のカテゴリーに散在しています。(たとえば、“JavaScript”や ”Ruby”、”Python”など) まだ、メモリ上のボブの全ての投稿を読み込みたいかもしれませんが、 今現在は“JavaScript"カテテゴリーでの投稿だけを表示したいとします。ここから“finding”が登場です。

    クライアント上のドキュメントの一部を選択
    クライアント上のドキュメントの一部を選択

    サーバーで行ったのと同様に、 データの一部分を選択するためPosts.find()関数を使いましょう。:

    // on the client
    Template.posts.helpers({
      posts: function(){
        return Posts.find({author: 'bob-smith', category: 'JavaScript'});
      }
    });
    

    今や、パブリケーションとサブスクリプションの役割が何であるか把握しているので、より深く掘り下げて、 共通する実行パターンを確認しましょう。

    Autopublish

    Meteorのプロジェクトをゼロから(つまり、meteor createを使って)作っているとしたら、 自動的にautopublishパッケージが有効となっています。 出発点として、autopublishが正確に何をしているのか、お話しましょう。

    autopublishの目標は、あなたのMeteorアプリをコーディング始めるのが非常に簡単にすることです。 それは自動的にパブリケーションとサブスクリプションを調整し、サーバーから全データをクライアントにミラーリングすることによって行われます。

    Autopublish
    Autopublish

    どうやって動いているのでしょうか? サーバー上に'posts'という名前のコレクションがあるとします。 autopublishは自動的にMongoDB内のpostsコレクションを見つけ全ての投稿をクライアント上で'posts'という名前のコレクションに送ります(コレクションは1つだと想定します)。

    そのため、autopublishを使うなら、パブリケーションについて考える必要はありません。 データはユビキタスで、物事がシンプルです。 もちろん、すべてのユーザーのマシン上で、アプリのデータベースの完全なコピーをキャッシュするのは明白な問題があります。

    こうした理由から、autopublishは開発を始めたときだけに適しています。 その時はまだパブリケーションについて検討しないで良いからです。

    すべてのコレクションをパブリッシュする

    一旦autopublishを削除したら、クライアントから全てのデータが消去されたことがすぐにわかるでしょう。 元に戻す簡単な方法は、autopublishが行うことを単に真似て、 コレクションを丸ごとパブリッシュすることです。例えば:

    Meteor.publish('allPosts', function(){
      return Posts.find();
    });
    
    全コレクションをパブリッシュする
    全コレクションをパブリッシュする

    すべてのコレクションをパブリッシュしているわけですが、少なくともどのコレクションをパブリッシュするか、しないかを制御しています。 この場合は、Commentコレクションではなく、Postsコレクションをパブリッシュしています。

    コレクションの一部分をパブリッシュする

    次の制御レベルでは、コレクションの一部分だけをパブリッシュします。 たとえば、ある著者に関連したpostsだけなら:

    Meteor.publish('somePosts', function(){
      return Posts.find({'author':'Tom'});
    });
    
    コレクションの部分パブリッシュ
    コレクションの部分パブリッシュ

    舞台裏

    もしあなたがMeteor publication documentationを読んだなら、

    おそらく、クライアント上のレコードの属性を設定するadded()ready()を使用しての話に圧倒され、 二重に苦労するでしょう。ですがMeteorアプリを作る上でそれらを使うことはありません。

    その理由はMeteorはとても重要で便利なものを提供しているからです: _publishCursor()メソッド

    これまでにこれが使われているのを見ましたか?おそらく直接は見ていませんが、あなたはパブリッシュ関数でカーソルを返しています。(例えばPosts.find({'author':'Tom'})) まさにこれをMeteorが使っているのです。

    MeteorがsomePostsパブリケーションがカーソルを返したところを見かけたら、 _publishCursor()を呼び出して自動的にカーソルをパブリッシュするとあなたは推測します。

    _publishCursor()が行うことは次のようなことです:

    • サーバーサイドのコレクションの名前をチェックします。
    • カーソルからマッチする全てのドキュメントを呼び出して、 それをクライアントサイドの同じ名前のコレクションに送ります。(これをするために.added()を使います)
    • ドキュメントが加えられたり、削除されたり、変更したときは、 クライアントサイドのコレクションにその変更をおくります。 (カーソルでは.observe()を使い、そして.observe().added().changed()removed()を使います)

    そのため、上記の例では、 クライアントサイドのキャッシュでユーザーの興味のある投稿(トムが書いたもの)だけが 見れるようにすることができます。

    一部のプロパティをパブリッシュする

    どのように投稿の一部分だけをパブリッシュするのか見てきました。 しかしもっと薄くスライスすることができます。 どのようにして特定のプロパティだけをパブリッシュするのか見ていきましょう。

    先ほどのように、 find()を使って、カーソルを返しますが、今回は特定のfieldを除外します。:

    Meteor.publish('allPosts', function(){
      return Posts.find({}, {fields: {
        date: false
      }});
    });
    
    一部のプロパティをパブリッシュする
    一部のプロパティをパブリッシュする

    もちろん、2つのテクニックを併用することもできます。例えば、トムが著者の全投稿を日付を削りつつ返すようにしたい場合、このように書きます。:

    Meteor.publish('allPosts', function(){
      return Posts.find({'author':'Tom'}, {fields: {
        date: false
      }});
    });
    

    まとめ

    あらゆるコレクションの全てのドキュメントのあらゆるプロパティをパブリッシュすることから始まって、(autopublishも含めて) コレクションの一部のドキュメントの一部のプロパティの一部だけをパブリッシュする方法を見ていきました。

    Meteorでのパブリケーション処理の基本をカバーしました。 このシンプルなテクニックは 大部分のケースで使えます。

    時々、パブリケーションをつなげる、リンクする、マージする必要が発生するかもしれません。 こうしたことは後の章でカバーしていきます!