次のことを考えてください。あなたは、6つの異なる言語で実装され、複数の異なるプラットフォーム上で稼働する完全に独立したWebサイトを持つ会社で働いています。独立したWebサイトのそれぞれが、個々のデータベースとスキーマを持っていて、様々なスキルセットのチームで管理され、アメリカ合衆国とヨーロッパをわたる8つの場所に位置しています。そして、その会社は成長しているのです。
あなたの仕事は? これらのばらばらなシステムで、その中にある重要なデータを適切に素早く共有することができるようにすることです。
あなたの設計基準は、以下のとおりです。
-
高トラフィック機能– サービスは、開始時で1日約1Mのデータを転送する必要があります
-
トランザクションの正確さ – サービスは、すべてのクライアントにとって、データの信頼できるソースとして間違いのないものでなければなりません
-
弾力性 – サービスは、フォーマットが変わる時に、シームレスにデータを再発行し、更新が容易でなければなりません
-
疎結合 –多くのシステムで、各々が独立してそれ自体を管理することができなければなりません
-
選定 – さまざまな言語(Java, C#, PHP, Ruby, and ColdFusion)で実装されたクライアントに対し、システムは参入の障壁を低くする必要があります。
-
適応性 – システムは、多くの異なるデータタイプをサポートする必要があり、要求に応じて、新しいデータタイプを追加するために拡張可能でなければなりません
私たちは、Homeaway.com(リンク)で約1年前にまさにこの問題に直面していて、そこで私たちは双方の仕事をしていました。そして、2つの設計の考えを認めるのにそう時間はかかりませんでした。第一に、分散されたパブリッシュ/サブスクライブサービスが、サブシステムの弾力性と疎結合に対処するために良い方法であるということです。第二に、(SOAPのような重量級のプロトコルと比較して)RESTfulサービスを構築することが、高可用性、拡張性、選定の容易さを必要としているシステムにとって、自然なソリューションであるということです。これらの2つの原則は、RESTful なパブリッシングプロトコルであるAtomと、Atomストアと呼ばれる新しい種類のデータサービスに、私たちを直接導いてくれました。私たちは、HomeawayのためにAtomストアを実装するために、去年1年を費やしました。そして、実世界での実現から、私たちはAtomサーバー ( http://www.atomserver.org) と名づけられたオープンソースのAtomストアフレームワークを切り出しました。本稿では、それについて説明します。
Atom
Atomは、2つの仕様で成り立っています。それは、Webフィードを記述するためのXMLベースの言語を定義するAtomシンジケーションフォーマット(リンク)と、そのフィードを取り出して操作するためのRESTfulなHTTPプロトコルを記述するAtomパブリッシングプロトコル(リンク)です。
Atomは、RSS(リンク) (Rich Site Summary)の代わりとして考えられ、一般に、ブログエントリといった人間が書いたテキストを含んでいます。したがって、Atomエントリやフィード (XMLエレメントとアトリビュート)の内部構造は、著者、言語、タイトルなどのようなパブリッシングの意味を伝達します。あなたをからかっているのではありません。Atomエントリは、それらのペイロードとしてさまざまなデータを運ぶのにとても適しています。
Atomエントリはデータの個々のレコードで、Atomフィードはエントリのリストです。AtomはRESTfulなプロトコルなので、リソースを(この場合、エントリとフィード)特定するURIのHTTPメソッドを実行することによって、リソースにアクセスします。たとえば、ブログエントリのフィードを取得するには、http://your-atomserver/entries/myblog のようなURIでGETをすることで実現されるでしょう。そして、そのレスポンスは、以下のようになります。
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<link rel="alternate"
href="http://your-blogserver/MyBlog"/>
<updated>2007-04-14T20:00:39Z</updated>
<title>My Weblog</title>
<entry>
<title>First Post</title>
<author>Chris Berry</author>
<link rel="edit"
href="http://your-blogserver/MyBlog/1234"/>
<updated>2007-04-14T20:00:00Z</updated>
<id>1234</id>
</entry>
<entry>
<title>Next Post</title>
<author>Bryon Jacob</author>
<link rel="edit"
href="http://your-blogserver/MyBlog/1235"/>
<updated>2007-05-01T17:00:00Z</updated>
<id>1235</id>
</entry>
</feed>
Atomは、エントリに適用することのできるカテゴリの概念もサポートしています。カテゴリは、任意の文字列タグで、あるグループの一部としてマークする目的でエントリに適用することができるものです。フィードURIは、フィルタリングを組み込むために修正されます。そして、それらに適合した特定のカテゴリを持つフィードからエントリのみを返します。
Atomの世界で特別な意味を持つ用語の組があります。エントリーは、ワークスペースとコレクションの固定された2つのレベルの階層でグループ分けされます。ワークスペースはいくつかのコレクションを含み、コレクションはいくつかのエントリを含みます。フィードのURIは、フィードのワークスペースとコレクションから成ります。
http://your-atom-server/workspace/collection
そして、エントリのURIは、ワークスペース、コレクション、エントリIDから成ります。エントリのための識別子は、所有しているコレクションの範囲内でユニークでなければなりません。
http://your-atom-server/workspace/collection/entryid
RSS のようなAtomは、Webシンジケーションフレームワークの基盤を提供します。そして、ブラウザーやニュースリーダーを含むAtomを解釈する既存のクライアントや、実際に一般的な言語で書かれたクライアントなどが数多く存在しています。加えて、Atomは、実際のところはHTTPとXMLの上にあるちょっとした慣習のセットなので、Webが利用できるプログラミング環境であれば何でも、簡単にアクセス可能です。
拡張とレイヤープロトコル
核となるAtomプロトコルは、フィードとエントリを操作するための基本的なオペレーションと、エラーのレポートとハンドリングのための方法を記述します。特に、拡張の概念を提供します。Atom拡張は、Atom XMLドキュメントの中で表現することができる追加的なXML要素と属性で、Atomをサポートするサーバーの振る舞いを変更するためにURIに適用することができる追加的なHTTPのリクエストパラメータです。以下の2つの重要な拡張があります。
-
OpenSearch (リンク) – どのような種類の検索がサポートされているかを決定するための、サーバーを内省する方法を含んだ検索のためのプロトコルを定義します。 OpenSearchに対応したサービスは、エントリとして表現された個々の結果とともに、Atomフィードとして検索結果を返します。
-
Feed Paging (リンク) – Atomフィードのための次へと前へのリンクタイプを定義することで、時系列データのページネーションに対処します。そのAtomフィードは、複数ページのfeedを通して前方や後方へのページをつけるためにクライアントが利用することができます。
一番目立って影響力のあるAtomの利用は、おそらくGData(リンク)(彼らの多くのサービスからデータにアクセスするためのGoogleのWeb API)です。GDataは、核となるAtomの仕様とOpenSearchだけでなく、Atomの仕様で解決されていない追加的な機能をカバーするための多くの独自拡張を取り入れています。
あなたがAtomエントリをブログまたはニュースフィードのようなWebコンテンツに制限せず、Atomを一般的なデータの管理まで広げるとき、あなたにはAtomストアが必要です。それは、内部リンクされたAtomエントリの包括的なデータストアで、Atomパブリッシングプロトコルを使用して編集し、 OpenSearchを使用して検索することができます。Atomサーバーは、自身のデータへの分散アクセスのための戦略を導入したいという思いから生じたものです。
データアクセスの分散制御
Atomプロトコルを利用するメリットの一つは、本質的に分散されたシステムの機能です。Atomサーバーでは、私たちはページネーションの概念を組み合わせ、feedの更新のためにサーバーをポーリングするメカニズムで、それぞれのリクエストで返るエントリの数を制限しています。クライアントは、feedの開始から始めて、ページのデータが無くなるまでリクエストを続けます。そして、クライアントは、定期的に新しいデータをチェックする必要があります。(すなわち、首尾よく処理された最後のページデータに別のページデータがあるかどうかということです)
AtomServer 自体は、あらゆる変化についてそれぞれのエントリにカウンターをインクリメントすることで、単純に印をつけます。そして、クライアントは読み込んだ feedごとに処理されたカウンタの最終値を保持する必要があります。feedに対する次のポーリングは、データの次のページの開始インデックスが、前のページの終了インデックスに設定されます。この方法で、Atomサーバーが高容量のデータの変更を素早く処理するケースに効果的に対処します。複数のエントリは、1秒のうちに変わることができました。
開始インデックスなしでfeedを得るとき、デフォルトで0から始まります。たとえば、
GET http://your-atom-server/widgets/acme
は、feedの子要素として、http://atomserver.org/namespaces/1.0/ 名前空間のendIndex という名の拡張タグを返します。これは、取得したページでの最後のインデックスとなります。
GET http://your-atom-server/widgets/acme?start-index=23
リクエストされたページに提供するための新しいデータがないときは、サーバーは304 NOT MODIFIED のレスポンスを返します。これは、次のページデータを求める前に設定されたポーリング間隔を待つことが望ましいという意味です。
POSTとPUTでエントリの一意性を管理する
Atom では、それぞれのエントリが、所有するワークスペースとコレクション内でユニークなIDを持つということは重要なことです。これら3つのコンポーネントは、エントリのURIをそれに対する一意の識別子とし、サービスの中で他のエントリとの識別が可能です。Atomサーバーは、POSTとPUTの2つの異なるHTTPメソッドを利用して新しいエントリの作成をサポートします。
あるエントリがPOSTで作成されるとき、使用されるURIは新しいエントリが挿入されるコレクションへのURIです。この場合、AtomサーバーはエントリIDを新しいエントリに割り当て、そのIDをレスポンス部に入れてPOSTを呼び出し元へと返す役割を果たします。
POST http://your-atom-server/widgets/acme
そうではなく、エントリがPUTでつくられるとき、エントリIDを割り当てることに対する責任はPUTをおこなうクライアントにあります。PUTで作られるURIは、作られるであろうエントリへのURIです。
PUT http://your-atom-server/widgets/acme/1000.xml
既存のエントリを更新することは、エントリのURIに対してPUTをおこなうことによってなされます。その意味で、PUTでエントリをつくることは、「レイジー」な更新に似ています。対象のエントリがないならば作成され、そうでなければ既存のエントリは更新されます。
楽観的な同時並行性でデータ整合性を保証する
高分散されたAtom世界の一貫性のある予測可能なデータを保証するために、Atomサーバーはシステムへの書き込みを管理するために楽観的同時並行性を使用します。楽観的同時並行性は、Atomサーバーへの書き手が、自身が編集しているリソースに関する現在のリビジョン番号を知っている必要があります。そして、書き込み操作が完了できるであろうと仮定して、リソースへの書き込みをしなければなりませんが、誰かがその間にそのリソースを書き込んだケースでも、優雅に対処することができます。
たとえば、システムAとBが両方とも、与えられたデータfeedでAcme Widgetsを変更したいと仮定します。まず、Aが現在の表現Acme Widget 123を問い合わせます。
GET http://your-atom-server/widgets/acme/123.xml
そして、そのfeedのレスポンスで以下の「editのリンク」が返されます。
<link href=”/widgets/acme/123.xml/2” rel=”edit”/>
Aは、123.xmlの表現の編集をおこないます。Aがその操作をおこなっている間、Bは現在のバージョンの123.xmlを問い合わせ、同様のレスポンスを得ます。Bの編集はAのそれより時間がかからないので、Bはeditのリンクに対して、すぐにその変更を書き込みます。
PUT http://your-atom-server/widgets/acme/123.xml/2
それが成功し、200 OKが返されます。そして、Bの編集が成功したということをAtomサーバーに知らせます。そこで、Aがその編集を終え、同じeditリンクに書き込みをしようとしますが、Bの編集によってリビジョン番号が既に更新されています。その結果、Aは409 CONFLICT のHTTPエラーを受信します。それは、サーバーからリソースの一覧を最後に参照してから、更新しようとしていたデータに対し、誰かがそのリソースを更新したということを示しています。この場合、AはGETでリソースを再取得し、新しいeditリンクを得て、処理を繰り返します。注意すべき点は、AがBの変更を既に含んでいる/widgets/acme/123.xml のコピーを変更することができるので、AがBの変更をむやみに上書きしないようなシステムにすることです。
多くのシステムでは、与えられたデータセットに対し、単一の、信頼できるライターがあるだけです。そのケースでは、楽観的並行性を管理するオーバーヘッドを減らすために、リビジョン番号としてアスタリスク(*)を与えることによって、楽観的並行性を無効にすることを可能にしています。
PUT http://your-atom-server/widgets/acme/123.xml/*
しかし、与えられたリソースへの書き込みがただ一人であるということが確実に分かっているときだけ利用するということが重要です。
カテゴリクエリ
Atomでは、カテゴリは値の組(SchemeとTerm)としてエントリを特定します。Schemeは、基本的にカテゴリの「名前空間」で、Termは、名前空間の特定の値です。
GDataの拡張をAtomから取り入れ、Atomサーバーでは、クライアントがエントリに適応したカテゴリによるfeedのフィルタリングをおこなうための、特定のfeedシンタックスを許可します。たとえば、「red」色を持っているものとしてタグづけられた、Acme Widgetsだけのfeedを得るためには、リクエストは以下のようになります。
GET http://your-atom-server/widgets/acme/-/(urn:colors)red
複数のカテゴリーが指定される場合、所定のカテゴリ(BooleanのAND)全てもつエントリだけが受信されます。たとえば、
GET http://your-atom-server/widgets/acme/-/(urn:colors)red/(urn:size)big
は、bigとredをもつAcme Widgetsの全てを返します。ANDとORを使うには、接頭辞の表記で指定することもできます。
GET http://your-atom-server/widgets/acme/-/OR/(urn:colors)red/AND/(urn:size)big/(urn:color)blue
これは、redかbigとblueのいずれかのAcme Widgetsの全てを返します。これらのfeedは、正確に他のどのfeedとも同じクライアントで処理されなければなりません。それらは、カテゴリなしのfeedと同様にポーリングとページネーションをおこなうことができます。
Atomサーバー
Atom サーバーは、すぐに利用できるAtomストアの実装です。それは、JavaのWebアプリケーションとして実装されていて、J2EEのサーブレットコンテナへのデプロイを必要とします。それに基づき、RESTfulな操作とAtomのXMLボキャブラリを処理するために、AtomサーバーではAbdera(リンク)と呼ばれるApacheプロジェクトのAtomプロトコルのオープンソース実装を利用しています。
Abdera は、既存のアプリケーションにAtomフロントエンドを加えるための優れたライブラリです。対照的に、Atomサーバーは成熟したAtomストアです。難しい設定をすることなく、格納する必要のあるコンポーネント全てを提供し、Atomエントリ自身の内容だけでなくAtomのメタデータと相互作用します。
Atomサーバーのプロトコルは、GDataのプロトコルから適切なものを全て取り入れています。一部の場合において、私たちはURLの読みやすさや、単純なクエリー構造や、GDataの仕様によってカバーされていない機能を実装するために、すこし違った決定をしました。
Atom サーバーは、リレーショナルデーターベースのエントリに関連するAtomのメタデータ全てを管理します。そして、リレーショナルデーターベース、ファイルシステム、特定のニーズに依存したもののいずれかで、実際のエントリの内容を格納します。Atomサーバーは、Atomプロトコルのすべての側面(URI の解釈、Atom要素のパース、拡張、タイムスタンプ更新、エントリのカテゴリ化)を自動的に処理するので、あなたはサーバーの変更をパブリッシュし、 feed変更の間隔でポーリングをする必要があるだけです。
Atomサーバーは使いやすいです。それは、単一のWARファイルや組み込みのJettyサーバーの中で動作するスタンドアロンサーバーとしてもデプロイできます。大部分のアプリケーションは、非常に少ない設定(アプリケーションのAtomワークスペースやコンテントストレージを構成するためのいくつかの Spring Bean)を単純におこなうことで、Atomサーバーを利用することができるでしょう。
結論
Atomサーバーには、私たちがまだ取り上げていない、重要で高度な機能がいくつかあります。今回はそれらの機能を説明するスペースがありませんが、それについては次回に説明する予定です。
-
Atomカテゴリの自動タグ- 登録か更新の際の、「自動タグ」エントリのための簡単な構成メカニズム。XPathAutoTagger が組み込まれ、コンテンツへのXPathを可能とし、条件付きでそれとAtomカテゴリを関連付けることができます。
-
バッチオペレーション- 登録、更新、削除リクエストが混じった「バッチ」操作をフルサポートしています。
-
Aggregate feeds-異なるコレクションやワークスペースから、さまざまなエントリをAtomカテゴリを使用してaggregateエントリに結合するための強力な機能。これらのaggregateに関するfeedリクエストの機能を含みます。あなたが複数のfeed操作を促進する(あなた自身が情報を結びつける必要がある) 代わりに、単一のaggregate feedをリスンすることができます。それは、すべての部分の変更を反映します。
AtomServer は、本物です。それは、私たちの会社で使用する生きた製品で、1日に100万以上のリクエストを処理し、数百万エントリを格納します。Atomのような RESTfulな仕様で構築することは、GDataのような既存のサービスの設計を活用することで、構築のための確かな基盤を保証することができます。私たちは、あなたがコピーを手に入れ、あなたが何を考えているかを私たちに教えてくれることを望んでいます。Atomサーバーは、http://www.atomserver.org から取得でき、数分で実行することができます。
原文はこちらです:http://www.infoq.com/articles/atomserver
(このArticleは2008年7月2日に原文が掲載されました)