2010年10月31日

Design of data model

データモデルの設計

クライアントとサーバでコンピューティングを分離するモデルはかなり以前からありますが、 その中でやり取りされるデータモデルに関してはベンダー依存でした。 しかし、Web の普及によってプロトコルは HTTP がデファクトスタンダードと言ってもよい地位を確立し、 Web 2.0 やら Ajax というバズワードの裏では、RSS や Atom といった形式でのデータ交換が促進されてきました。 Facebook, Google Buzz, Twitter といった即時性を追及するサービスでは、冗長なデータ構造を使っていると 自分のネットワークトラフィックを浪費してしまう、という問題も出てきました。

2010年以降数年の流行語は「ソーシャル」か「リアルタイムWeb」あるいは「セマンティック」だと思いますが、 そこで使うデータはどういうものなのか、ということに関してメモしておきます。 特にまとまりも結論もありません。

データ出力の基本

まずは先人に学ぶのが良いと思いますので Google の API 設計方法 のビデオを観ます。 Google の場合は "API CONFIGURATION TOOL" という社内ツールがあるそうです。 protobuf で定義した operation と REST のパスをマップしてくれるようです。

Atom を基礎として設計する場合は、フィードとエントリ (feed and entries)、 リソースの集まり (collection of resources) という概念が重要になります。 ざっくりとした XML で表現すると次のようなデータ構造になります。

<feed>
  <title>リソースのコレクション</title>
  <link rel="alt" href="http://example.com"/>
  <entry>
    <title>リソース その1</title>
    <content>リソースの中身</content>
  </entry>
  <entry>
    <title>リソース その2</title>
    <content>リソースの中身</content>
  </entry>
</feed>

多くのサービスではフィードは全部取得する形式ですが、Google の場合はクエリパラメータでフィールドを指定する (fields=entry(title,content) のような感じ) ことで、部分的にフィードを取得できるようです。 これを利用するとデータの差分だけを管理すれば良くなりますので、ネットワークに流すデータ量を節約できます。 実際、Google が公開している Java のライブラリには 差分データ用のクラス があります。

複数のデータフォーマット

Atom だけでなく JSON でもデータを出力する場合には、POJO_ のようなモノからマップできると管理が簡単になります。 それぞれの生成ロジックを手作業で管理することももちろん可能ですが、似たようなことを繰り返すのは労力の無駄が大きくなります。 また、データのキーがバラバラになってしまうと、操作ルーティンの記述が大変です。 例えば、Atom では title でアクセスするものが JSON では name だったりすると悲劇を招きかねません。

アクセスするためのキー名としては、トップの名称が大事であることも多くなります。 data だったり results だったりしますが、個人的には data が良いと思います。 コールバック関数で受け取ったときに event.data のようにアクセスできると、他の API との親和性も高くなるためです。 キーの名前を独自に定義しても構わないとは思いますが、その分ドキュメントをたくさん書かなくてはいけませんので、 しわ寄せを覚悟しておいた方がよいでしょう。

従来の JSON ではなく、 JSON-C というデータの作りも増えてきています。

JSON-C, sometimes called "Compact JSON," is a compact alternative to the Atom XML data format

JSON-C は Atom に比べて半分くらいのデータ量で、gzip 圧縮すると 30% 小さくなるそうです。 New JSON format for the Google Calendar API というブログ記事が詳しいので一読すると良いと思います。 Google のカレンダーサービスではパラメータに alt=jsonc と指定すると利用できるそうです。 なお、YouTube の API では 2010 年の 2 月から利用できるようになっています。 これが一番早かったのかもしれませんね。

Google のサービスの場合は alt パラメータ (alt=json-in-script など) で出力データ形式を指定できます。 Twitter はリクエストするエンドポイントの拡張子でレスポンスのフォーマットが決まります。 XML, JSON, Atom, RSS のいずれかです。リクエストを発行するときのペイロードが影響するかは確認していません。 facebook の出力フォーマットは format パラメータで指定します。

データフォーマットに関しては、正常系だけでなく異常系も統一的に扱えることが望ましいと思います。 XML でリクエストを発行したのにレスポンスが form-urlencoded だとげんなりするかもしれません。 エラーメッセージに何を含めるかはサービスのポリシーに依存する問題になりますが、 facebook からのエラーを意味する JSON は次のように返ってきます。

{
   "error": {
      "type": "OAuthAccessTokenException",
      "message": "An access token is required to request this resource."
   }
}

大雑把に XML と JSON のデータを確認しました。 XML には DTD の検査ツールがたくさんありますので、まずは Atom でデータ構造を検討してから、 データ量の小さい JSON に変換して、よりデータ量の小さい JSON-C を使うようにする、という流れでしょうか。 もちろん、いきなり JSON でデータを定義した方がキー名の自由度が高いので素早く開発できるかもしれませんが、 オレオレなデータ構造になりやすく、日本人の場合は英単語の間違いもあったりしますので、まずは Atom で定義するのが良いと思います。

Access Control Allow Origin

データ構造が決まってそれを HTTP で送受信できるようになると大抵のことは実現できますが、 Web ブラウザのことを考えると特殊な HTTP ヘッダーを付与する工夫も検討した方が良いでしょう。 Web ブラウザには Same Origin Policy という機構が組み込まれており、 自分をサーブしているホスト以外からの JavaScript によるデータ受信を制限しています。

詳しいことは Mozilla のドキュメント (英語) を読めば分かると思います。 W3C も "Cross-Origin Resource Sharing" という規格を作成しています。

要するに、サーバ側でヘッダーを付与してね、ということなので、こんな感じ (AppEngine/Python のハンドラ) でヘッダーを付けてあげましょう。 扱うデータの内容にも拠りますが、許可する HTTP メソッドは検討した方が良いかもしれません。 ここでは POST, PUT, DELETE を許可しています。 テストスクリプトを localhost に配置している場合には、 originlocalhost にします。 何でも許可する場合には * にしておけば良いでしょう。

origin = "<TRUSTED-HOST-NAME>" # self.request から取得しても良いし "*" でも良い。
if origin:
    self.response.headers.add_header("Access-Control-Allow-Origin", origin)
    self.response.headers.add_header("Access-Control-Allow-Methods", "POST, PUT, DELETE")

クライアントサイドの実装は現在進行形ですので、リクエストを発行するときに利用するオブジェクトが Web ブラウザごとに異なる、などの問題もあります。 この辺は追々対応が進んでいくのではないかな、という希望的観測を抱いておくことにしましょう。 ブラウザがネイティブに対応していなくとも、ライブラリでそれらの差異を吸収することも考えられます。

蛇足ですが、この機構を利用すると OAuth の認証プロセスを JavaScript だけで記述できるようになります。 このようなサンプル (Webkit only) を用意しておくと、 サービスプロバイダーの実装をテストするときに便利になるでしょう。 また、JavaScript だけで記述できることは、モバイル機器や Adobe AIR への流用を簡便化してくれることも意味します。 ネイティブクライアントや xAuth を実装しなくても良くなると有難いですね。(誰が?というのはあるけど)

クライアントライブラリ

サーバが複数のデータフォーマットをサポートするようになると、クライアント側もそれに追随していく必要があります。 パフォーマンスの向上を見込めるのは嬉しいと思いますが、既に動作しているソフトウェアを書き換えるほどのメリットがあるかは、 ケースバイケースにならざるを得ません。 しかし、Google の場合は gdata-java-client, gdata-python-client といったライブラリを公開しています。 PHP や JavaScript などもあります。 なお、 gdata-java-client は非推奨 (deprecated) ではないものの開発停止になっており、 google-api-java-client がお勧めのようです。

クライアントライブラリを使うと、データフォーマットだけでなくバージョンの違いも吸収してくれます。 データを解析する部分と、リクエストを発行する部分がそれに該当します。 たとえば、GData の場合は HTTP のヘッダーでバージョンを指定します。 普及バージョンは 2.0 で、3.0 が Labs のステータスになっています。

GData-Version: 2.0

GData のバージョン 2.0 では認証に AuthSub という独自形式を用いています。 しかし緩やかにではありますが、AuthSub から OAuth へと移行していますので認証プロセスも変わってきています。 ライブラリをうまく使っていれば比較的簡単に移行できるのではないでしょうか。

GData のバージョン 3.0 には discovery 機構が導入されています。 一部の API (Buzz など) では使えるようですが、本格的な設計と実装はこれからのようです (2010年の夏くらい現在)。 discovery とは、文書から URL をスクレイピングしなくても良いように標準化する仕組みです。 ある文書とどの文書が関連づいているかを管理するためのもの、と考えれば良いのではないでしょうか。 6月の "GData Python Client V3" というブログエントリでプロトタイプが公開されています。 依存するのは次のライブラリくらいのようです。 なお、OAuth のライブラリは oauth2 ですが、利用する仕様はバージョン 1 です。

やや脱線しますが、Python のライブラリは V3 の discovery 対応を進めていますが、 Java のライブラリは違うプロジェクトに移行している点が興味深いと感じました。 API ドキュメントを読むと分かりますが、Java のライブラリには明らかに AppEngine / Android を意識している部分もあります。 プロトコルを決めるチームとプラットフォームを拡張するチームがあるのかもしれませんね。

クライアントライブラリは Google だけでなく、 Twitter LibrariesFacebook の github もあります (Twitter の github もあるけど)。 Android や iPhone のライブラリを公開している点が今どきな感じですね。

その他のデータ仕様

GData V3 の問題意識と似ていますが、情報が即座に拡散するリアルタイムWebでは、 元ネタがどこだったのか?どのデータとどのデータに関連があるのか?という情報が希薄になりがちです。 複数のサービスにマルチポストするクライアントの使用が加速してくると、これが顕在化してきます。

どうあるべきか?という問いへの答えはないと思いますが、今のところはこれらの仕様を押さえておくのが良いのかな、と。

  • Webfinger ... finger コマンド (user information lookup program) の Web 版。
  • XRD ... リソース記述フォーマット。 (Extensible Resource Descriptor)
  • LRDD ... XRD のベースとなるもの。「ラード」と発音。 (Link-based Resource Descriptor Discovery)
  • Atom ... Atom と AtomPub があるから、入力も出力も共通のフォーマットでカバーできる。
  • ActivityStreams ... Web 上におけるソーシャル活動の配信フォーマット。
  • Pubsubhubbub ... サーバ同士のパブサブプロトコル。Atom や RSS の拡張。
  • Salmon ... 各種フィードによって断片化していくコメントを、逆に辿れるようにするデータ仕様。
  • Portable Contacts ... ユーザーのコンタクトリストを安全に利用するための API。

途中であったり今後の変更は多々あると思いますが、 StatusNetDiaspora にその実装を見ることができます。

StatusNet is the open source microblogging platform that helps you share and connect in real-time within your own domain.

Diaspora: The privacy aware, personally controlled, do-it-all, open source social network.

終わりに

主に GData 周辺のことについてまとめました。 これまで、いま、これから、という流れで考えると、XML で定義して JSON などに変換するのが良いのかな、と。 なお、基盤システムのような閉じた空間で利用する場合は Apache ThriftMessagePack あるいは protobuf でさらに高効率化を図るべきでしょうが、インターネット上のデータの受け手 (ユーザーエージェント) がそのデシリアライズに対応しているとは限りませんので、今のところは JSON-C がベターだと思いました。

実際の利用シーンで考えると、データ単体で完結することはありませんので、それらをどのように関連づけるかが難しくなります。 OWL のようなオントロジーを利用すれば実現できるのかもしれませんが、 関連づけが発生すると、今度は個人情報/プライバシーの問題にぶつかります。 誰に何をどこまで公開するのか?という話題です。 Facebook がプライバシーポリシーを定期的に変更していることは周知のことかもしれませんが、 未だに決定打を打ち出せない、というのが現実です。 WebID が解決の一助となるかは、これから考えるべきことなのかもしれません。

コメントを投稿