OpenSocial のデータをCouchDBドキュメントで表現する。

people.get と activities.get の実装がひとまず完成しました。sortOrder とか networkDistance とかはまだですが。以下No-SQL所感。

データモデルが楽すぎる件

とりあえずPeopleをどうやってCouchDBに入れようか、と思ったんだけれど、以下で終了w

  {
    "type"  : "Person",
    // singluar person fields
    // required
    "_id"   : "example.org:yssk22",
    "displayName" : {
      "formatted" : "yssk22"
    },
    // optional
    "anniversary" : "1990-01-01",
    "birthday"    : "1980-01-01",
    "name"  : {
      "formatted" : "Administrator"
    },
    "gender" : "male"
  },

OpenSocial における Person オブジェクトは、http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/REST-API.html#personFields にきれいにデータが定義されているようです。で、普通なら、これをうまくテーブルにわけてーとやるのですが、そこはCouchDB、そのまま入れてしまってかまいません。

さらに、userId=foo, groupId=@friends という引数の場合は、「userIdがfooの友人を一覧せよ」ということなのです。RDBだと、友人関係を作るために、N:N のテーブルを作ってJOINを唱えたりするものですが、Relationship という人間関係ブックマークドキュメント
で表現してしまえば、なんてことはないのです。

Relationship ドキュメントを用意し、

  /**
   * Relationship definitioin for yssk22
   */
  {
    "type"  : "Relationship",
    "from"  : "example.org:yssk22",
    "to"    : "example.org:joe-doe",
    "tags"  : ["friends"]
  }

View でtagsをばらせば、マッチするリレーションを取得できます。

function(doc){
  if(doc.type == "Relationship"){
    for(var i in doc.tags){
      // key includes doc.to to be sorted.
      emit([doc.from, doc.tags[i], doc.to], null);
    }
  }
}

startkey=["foo", "friends"]&endkey=["foo", "friends", "\u0000"] とすれば、友人(とマークしている)リストは一気に取得できます。これで @friends には対応できましたし、groupId="blocklist" のようなタグ付けもできます。

「双方向にタグ付けされている関係」を抽出するのは、現時点のCouchDBのView機能では少し大変なので、データモデル自体を再考する必要があります。ただし、現実世界で「相手にラブレターを一方的に送りつけた瞬間に恋人関係が成り立つ」ことは残念ながらないので、双方向の関係は「あとで処理する」メソッドが使えます。この辺はまた後で。

最後にActivitiesですが、これはOpenSocialのスペック上もデータ定義は曖昧なようです(Containerの好きなように、ただし、JavaScriptopensocial.Activity オブジェクトのインターフェースは実装してね、という意味で)。なので、ひとまず、

  {
    "_id"     : "test_activity_1",
    "type"    : "Activity",
    "title"   : "Example Activity",
    "body"    : "This activity is an example for activity documents.",
    "userId"  : "example.org:yssk22",
    "appId"   : "test"
  },

感じで、必須になるであろうフィールド(userId, appId, title, body)だけ定義しておいて、あとで考えることにしました。

クエリは大変かもしれない?

sort とか フィルタ とかがMapReduceのKeyに邪魔されるので、どうしてもJSON-RPCのプロキシであるRuby/Rackアプリケーション側で実装しなければならないものがでてきます。データ量が増えると重くなりそうなのですが、Key-Value という性格を利用すると比較的キャッシュが容易なので気にしないことにします。

そもそも個人が自分専用に立てるOpenSocialアプリが動くホームページ、レプリケーションとればいつでもどこでもつかえるよ!的なものを目指そうかと思うので、データ量が増える、というのはあまり考えなくてもいいのかも。twitter でも10K人のFollowerがいますってわけでもないんだし。

問題は、iGoogle がそんな雰囲気になりだしたので、どうしようかと。勝手にGmailのアドレスがソーシャルネットワークになっている罠にはたまげた。いくらFacebookに対抗するとしても、奥の手過ぎる。