レベル: 中級 Dan Jemiolo (danjemiolo@us.ibm.com), Advisory Software Engineer, IBM
2007年 10月 16日 Project Zero の主な目標の 1 つは、リッチ・インターネット・アプリケーション (RIA) を簡単に作成できるようにすることです。RIA アプリケーションの優れた例には、Flickr 写真共有サービスがあります。REST の原則と Ajax (Asynchronous JavaScript + XML) 手法、そして動的スクリプト言語を使って設計された Flickr は、ユーザー・フレンドリーであるだけでなく、スケーラブルで拡張可能なサービスを提供しています。このように他の RIA 作成者が目指している特徴の数々を備えた Flickr を Zero で再現すれば、RIA プラットフォームとしての Zero を検証する最良の方法となるはずです。この記事では、既存の Zero コンポーネントを組み合わせて、今日の Flickr が提供する機能の多くをサポートする写真共有サービスを作成します。この過程を通して、RESTful な設計、HTTP によるコンポーネントの接続、そして Zero にはまだ組み込まれていない機能を JavaScript を使って実現する方法を学んでください。
始める前に
この記事では、読者が Project Zero をダウンロード済みであること、そして初心者向けのチュートリアルを完了しているか、または単純なアプリケーションを自分自身で作成した経験があることを前提とします。また、REST の基本原則と各種 HTTP メソッド (GET、POST など) について十分な知識があることも必要です。REST の概要については、「参考文献」セクションを参照してください。
写真共有アプリケーションの設計概要
Yahoo! の Flickr 写真共有サービス (「参考文献」を参照) は、卓越した RIA の一例です。Flickr には他の RIA 作成者が目指している特徴の数々が備わっているため、このようなアプリケーションを Zero で再現することが、優れた RIA 開発プラットフォームとしての Zero を検証することになります。幸い、写真共有サービスを一から作成する必要はありません。Zero プラットフォームにはブログ、レーティング・システム、プロファイル・マネージャーなど、独自のアプリケーションを構築する際に再利用できる RIA コンポーネントが数多く組み込まれているからです。以降のセクションでは、写真共有サービスの性質を説明し、このタイプのアプリケーションには現在 Zero に備わっているコンポーネントと多くの共通点があることを明らかにします。
写真共有アプリケーションの設計
写真共有サービスを作成すると言うと、最初は大掛かりな取り組みのように思えます。しかしよく考えてみると、このタイプのアプリケーションは 2 つのよく知られた Web 2.0 アプリケーションと多くの点で共通していることがわかります。その 2 つとは、ブログ・アプリケーションとファイル共有アプリケーションです。Flickr は基本的にはブログ・サービスで、ブログ投稿の表示はよりドメインに特化したものになっています。ユーザーのコレクションに含まれる写真のそれぞれがブログ投稿として表され、写真 (ブログ投稿) はアルバム (ブログ・カテゴリー) に分類されて説明用のタグが付けられます。さらに、Flickr はその大衆向けのユーザー・インターフェースを介さなくても、ユーザーが自分たちの実際の画像ファイルにアクセスできるようにしています。これは、画像ストレージはブログ・サービスとは独立したハイエンドのファイル共有サービスによって操作されることを意味します。Project Zero のオプション・ライブラリー一式にはブログ・コンポーネントとファイル共有コンポーネントの両方が含まれているので、これらのコンポーネントを組み合わせて同様の写真共有の体験を作り出すことができます。
この記事の写真共有 UI の実装では、ファイル共有コンポーネントを使って写真の格納をし、ブログ・コンポーネントを使って写真の整理をします。ユーザーがファイル共有やブログのコンテンツをブラウズすれば、そこにある自分の写真を見ることができますが、この写真共有 UI はそれより遥かに写真中心でユーザー・フレンドリーです。Flickr の UI を極めて感動的なものにする手の込んだ Ajax または Flash 駆動のウィジェットをどうやって作成するかについてはこの記事では説明しませんが、この UI を見ると、このウィジェットの振る舞いを制御するために必要な RESTful なインターフェースとロジックがわかります。優れた設計の写真共有サービスを配備すれば、それをベースにどんなタイプの UI でも追加できるはずです。
それでは早速、Zero のブログ・コンポーネントおよびファイル共有コンポーネントの設計の詳細を検討し、この 2 つのコンポーネントをどれだけ簡単にサンプル写真共有アプリケーションに適用できるかを調べてみましょう。
RESTful なリソースをビルディング・ブロックとして使用する
ブログ・コンポーネントとファイル共有コンポーネントはどちらも、HTTP メソッドで操作できる 1 つ以上のリソース・タイプで構成されています。各 HTTP メソッドが表すのはリソースでの読み取りまたは書き込み操作、そして各 HTTP URI が表すのは個々のリソース・インターフェースです。一例として、http://www.example.com/blogs/jsmith/posts/our-latest-vacation に対して HTTP GET を実行すると、ユーザー jsmith の Our Latest Vacation というタイトルのブログ投稿の表示が返されます。同様に、ユーザー jsmith のブログに新しいブログ投稿を作成するには http://www.example.com/blogs/jsmith に対して HTTP POST を実行します。表 1 に、HTTP メソッドと URI を使用して Zero のブログ・サービスと対話する方法を、クライアントとサーバー間で交換されるデータ・フォーマットと併せて記載します。太字で示した URI トークンは変数です。
表 1. Zero ブログ・コンポーネント用 REST インターフェース
| URI | HTTP メソッド | データ・フォーマット | 説明 |
|---|
| /blogs | POST | JSON | リクエスト本体で送信された JSON 表現を使用して新規ブログを作成する | | /blogs/blog-name
| GET | JSON | 指定された名前を持つブログのリソース定義を取得する | | /blogs/blog-name
| PUT | JSON | 指定された名前を持つブログのリソース定義を更新する | | /blogs/blog-name
| DELETE | None | 指定された名前を持つブログをそのすべての投稿およびコメントとともに削除する | | | | | | /blogs/blog-name/posts | POST | Atom | リクエスト本体で送信された JSON 表現のブログで新規投稿を作成する | | /blogs/blog-name/posts/post-id
| GET | Atom | 指定された ID を持つブログ投稿のリソース定義を取得する | | /blogs/blog-name/posts/post-id
| PUT | Atom | 指定された ID を持つブログ投稿のリソース定義全体を更新する | | /blogs/blog-name/posts/post-id
| DELETE | なし | 指定された ID を持つブログ投稿を削除する |
HTTP で認証および許可を追加すると、この API が 1 つまたは複数のブログを管理するために必要なすべての関数を提供していることがわかります。API は Dojo などの Ajax ツールキットを使って簡単に呼び出せるので、開発者はページを更新することなくレスポンスに含まれるデータを UI ウィジェットに統合することができます (Dojo Toolkit についての詳細は、「参考文献」のリンクを参照してください)。もちろん Apache HttpClient (Java™ 技術の場合) や cURL コマンド・ライン・ツール (C の場合) といった一般的な HTTP クライアントで API にアクセスすることも可能です (「参考文献」にリンクを記載)。さらに、Web ブラウザーのアドレス・バーを使って API の一部を呼び出すこともできます。ブログを作成、変更するには JSON (JavaScript Object Notation) を使用しますが、ブログ投稿は Atom で操作します (「参考文献」にリンクを記載)。表 2 に記載する同様の API は、公開ファイルおよびディレクトリーを格納するファイル共有コンポーネント用 API です。
表 2. Zero ファイル共有コンポーネント用 REST インターフェース
| URI | HTTP メソッド | データ・フォーマット | 説明 |
|---|
| /file-shares/user-name
| POST | JSON | ユーザーの JSON 表現を使ってそのユーザーのファイル共有に新規ファイルまたはディレクトリーを追加する | | /file-uploads/user-name
| POST | HTML フォーム・データ | HTML フォームを使ってユーザーのファイル共有に新規ファイルまたはディレクトリーを追加する | | /file-shares/user-name/path
| GET | JSON | ユーザーのファイル共有内の指定されたパスにあるファイルまたはディレクトリーを取得する | | /file-shares/user-name/path
| PUT | JSON | ユーザーのファイル共有内の指定されたパスにあるファイルまたはディレクトリーを更新する | | /file-shares/user-name/path
| DELETE | なし | ユーザーのファイル共有内の指定されたパスにあるファイルまたはディレクトリーを削除する |
ファイル共有 API で注目すべき点は、ユーザー名を使って共有ディレクトリーを分けていること、そしてファイルを追加する方法が 2 通りあることです。まず、すべてのユーザーにそれぞれ固有の共有ディレクトリーがあるため、ユーザーの写真共有アカウントの代わりに共有ディレクトリーを使って簡単に画像ファイルの格納場所を識別することができます。また、ファイルを追加する 2 通りの HTTP POST メソッドは、フォト・アルバム UI の設計という点で柔軟性をもたらします。つまり、HTML フォームを使ってデータをアップロードすることも、Flickr の Uploadr ツールのようなブラウザー以外のクライアントを使用することもできます (「参考文献」にリンクを記載)。
以上の API があれば、2 つのコンポーネントでの一連の操作としてサンプル写真共有サービスを設計することができます。コンポーネント実装の詳細について知る必要は一切ありません。この 2 つのコンポーネントとのすべての対話は、それぞれの RESTful な HTTP インターフェースによって行われます。次のステップは、写真共有アクションを HTTP リクエストにどう対応させるかを具体的に決定することです。
写真共有アクションをブログおよびファイル共有にマッピングする
アプリケーションの設計に取り掛かるには、まず表 1 と表 2 のような表を構成し、ブログ・コンポーネントとファイル共有コンポーネントに含まれる関数と新しく作成しなければならない関数を調べます。表 3 に、サポートする必要がある機能とそれぞれが呼び出すコンポーネント API を記載します (説明文のみ)。
表 3. 写真共有アクションとブログおよびファイル共有とのマッピング
| 写真共有アクション | コンポーネント内部での処理 |
|---|
| 写真共有アカウントの新規作成 | 写真をソートするための新規ブログを作成する
画像ファイル用の新規ファイル共有ディレクトリーを作成する | | フォト・アルバムの新規作成 | 新規ブログ・カテゴリーを作成する | | フォト・アルバムへの写真の追加 | 画像ファイルのコピーをファイル共有ディレクトリーにアップロードする 画像ファイルを指す <img/> タグを設定して新規ブログ投稿を作成する | | 写真をすべて取得 | 写真のブログに含まれるすべてのブログ投稿を取得する | | フォト・アルバム全体の取得 | 特定カテゴリーに含まれるすべてのブログ投稿を取得する | | 個別の写真の取得 | 該当する写真のブログ投稿を取得する | | 個別の写真とそのアルバムの更新 | 該当する写真のブログ投稿およびカテゴリー名を更新する | | 写真をすべて削除 | 該当する写真のブログを削除する オプションで、画像ファイルが配置されたファイル共有ディレクトリーを削除する | | フォト・アルバム全体の削除 | 該当するブログ・カテゴリーを削除する カテゴリーに含まれるブログ投稿を削除する オプションで、ブログ投稿と関連付けられた画像ファイルを削除する | | 個別の写真の削除 | 該当するブログ投稿を削除する オプションで、ブログ投稿と関連付けられた画像ファイルを削除する |
表 3 を見るとわかるように、「コンポーネント内部」で複数の操作が必要な機能は少数しかないので、作成しなければならないグルー・コードはわずかです。表 4 は表 3 と同様ですが、この表には機能の説明文ではなく、必要とされる正確な HTTP メソッドと URI を記載しています。
表 4. 写真共有アクションと HTTP リクエストとのマッピング
| 写真共有アクション | コンポーネント内部での処理 |
|---|
| 写真共有アカウントの新規作成 | HTTP POST to /blogs
HTTP POST to /file-shares/user-name
| | フォト・アルバムの新規作成 | H/blogs/blog-name/categories への HTTP POST | | フォト・アルバムへの写真の追加 | /files-shares/user-name への HTTP POST
/blogs/blog-name/posts への HTTP POST | | 写真をすべて取得 | /blogs/blog-name での HTTP GET | | フォト・アルバム全体の取得 | /blogs/blog-name/categories/category-name での HTTP GET | | 個別の写真の取得 | /blogs/blog-name/post/post-id での HTTP GET
| | 個別の写真とそのアルバムの更新 | /blogs/blog-name/post/post-id への HTTP PUT
| | 写真をすべて削除 | /blogs/blog-name での HTTP DELETE
/file-shares/user-name/photos での HTTP DELETE | | フォト・アルバム全体の削除 | /blogs/blog-name/categories/category-name での HTTP DELETE
/blogs/blog-name/posts/post-id での HTTP DELETE
/file-shares/user-name/photos/photo-file-name での HTTP DELETE | | 個別の写真の削除 | /blogs/blog-name/posts/post-id での HTTP DELETE
/file-shares/user-name/photos/photo-file-name での HTTP DELETE |
単一の HTTP リクエストに含まれるすべての機能は、前述の Ajax または HTTP クライアント・ライブラリーのいずれかを使って簡単に処理することができます。ただし、確実にすべての HTTP リクエストが (「トランザクション」として) 正常に完了するようにするには、複数の操作からなる機能にグルー・コードが必要になります。ここまでのところ、ブログやファイル共有とはまったく関係のない写真共有サービスの機能は 1 つも見つかっていません。つまり、Zero アプリケーションで新しい RESTful なリソースを作成する必要はないということです。
写真共有アプリケーションの実装
写真共有アプリケーションの概念を理解できたところで、いよいよ実装作業を開始します。このセクションでは、Zero の Eclipse プラグインまたはコマンド・ライン・ツールを使用して photo-share という名前の新規 Zero アプリケーションがすでに作成されていることを前提とします。手順の説明ではコマンド・ライン・インターフェースを参照しますが、対応する Eclipse のインターフェースもコンテキスト・メニューを見ればすぐにわかります。
ブログ・コンポーネントとファイル共有コンポーネントをインストールする
まず必要な作業は、ブログ・コンポーネントとファイル共有コンポーネントをアプリケーションに追加することです。それには /config/ivy.xml にあるアプリケーションの Ivy ファイルを開いて、以下の行を追加します。
リスト 1. 依存関係としてのブログおよびファイル共有コンポーネントの追加
<dependency org="zero" name="zero.services.blog" rev="1.0.0+"/>
<dependency org="zero" name="zero.services.share" rev="1.0.0+"/>
|
リスト 1 の XML を追加したら、コマンド・ラインから zero resolve を実行して 2 つのコンポーネントのインストールを終了します。コンポーネントの実際のコードと成果物がプロジェクトに追加されていないとしても、Zero の依存関係解決メカニズムより、独自のコードからコンポーネントへの参照は実行時に有効になります。
JavaScript を使用してすべてを繋ぎ合わせる
単一の HTTP リクエストに対応した写真共有アクション (表 4 を参照) は、Dojo JavaScript ツールキットを使えば至って簡単に実装することができます。Zero に含まれている最新バージョンの Dojo (0.4.3) をアプリケーションに追加するには、リスト 2 の XML を Ivy ファイルに追加します。
リスト 2. Dojo 依存関係の追加
<dependency org="dojo" name="dojo" rev="0.4.3+"/>
|
Dojo は HTML フォーム・データを簡単に HTTP リクエストに転送できるようにします。単一のリクエストのアクションに必要なのは、まさにこの HTTP リクエストへの HTML フォーム・データの転送だけです。リスト 3 に、新しいフォト・アルバムの名前を入力する HTML フォーム、そして HTTP POST を使って新規のフォト・アルバムを作成する JavaScript 関数を記載します。フォームから取得されたデータが JSON オブジェクトとしてリクエスト本体に直接追加される仕組みに注目してください。zero.services.blog の実装が受け取るのは JSON オブジェクトで、この実装はデータの加工をまったくせずにリクエストを処理します。
リスト 3. HTML および JavaScript によるフォト・アルバムの新規作成
<script type="text/javascript" src="/dojo.js">
</script>
<script>
dojo.require("dojo.io");
dojo.require("dojo.json");
dojo.require("dojo.widget.Form");
function createAlbum()
{
var form = dojo.widget.byId("CreateAlbumForm");
var albumData = form.getValues();
var blogName = getPhotoShareName();
dojo.io.bind({
url: '/resources/blogs/' + blogName + '/categories',
method: 'POST',
sync: false,
mimetype: 'text/json',
contentType: 'text/json',
postContent: dojo.json.serialize(albumData),
load: function(type, data) {
alert("The new album was added successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
}
function getPhotoShareName()
{
return <%= _gc.get("/request/subject/remoteUser") + "-photos"; %>
}
</script>
...
<form dojoType="form" id="CreateAlbumForm" name="CreateAlbumForm">
<table cellpadding="10" cellspacing="0" border="0" width="50%">
<tr>
<td><b>New Photo Album:</b></td>
<td><input type="text" size="64" name="categoryid"/></td>
</tr>
</table>
<br/>
<input type="button" onClick="createAlbum();" value="Submit" />
</form>
|
ご覧のように、createAlbum() 関数は HTML フォームから直接 JSON データを取得し (form.getValues())、そのデータを dojo.io.bind() のパラメーターとして HTTP リクエストに追加します。このリクエストは、URI を /blogs/blog-name/categories に指定した HTTP POST です (表 4 を参照)。HTML はユーザーに新規フォト・アルバムを作成していることを通知しているにも関わらず、内部で作成しているのはアルバムを表す新しいブログ・カテゴリーです。同様に、ユーザーの写真共有の名前 (getNameOfPhotoShare() メソッドによって指定) は、HTTP リクエスト URI を作成する際にブログの名前として使用されます。ユーザーは、アプリケーションがブログをどのように使用するかについて一切知る必要はありません。ユーザーが知るのは唯一、写真を整理する手段が用意されたということだけです。
残りの単一のリクエストアクションを実装するには、createAlbum() メソッドをコピーし、新しい名前を付けて別の HTTP メソッドを使用するように変更するだけで済みます (deleteAlbum()、method: 'DELETE' など)。いずれの場合にしても、フォーム・ボタンのクリックまたはページのリロードに応答して、表 4 の URI を使ったコンテンツの GET、POST、PUT、または DELETE が実行されます。
JavaScript でトランザクションを処理する
単一のリクエストのアクションはいったん dojo.io.bind() 関数に慣れてしまえば難なく実装できますが、マルチ・リクエスト・アクションを処理には多少の考慮が必要です。リスト 4 に、写真共有アカウントの新規作成を処理する HTML フォームと JavaScript 関数を記載します。createPhotoShare() メソッドは 2 つの HTTP POST リクエストを送信します。1 つは、ユーザーのファイル共有にディレクトリーを作成するためのリクエスト、もう 1 つは写真を整理するブログを作成するためのリクエストです。後者のリクエストのためのエラー・ハンドラーはデータが矛盾した状態のままにならないよう、前者のリクエストによる処理を取り消すことに注目してください。
リスト 4. HTML および JavaScript による写真共有の新規作成
<script type="text/javascript" src="/dojo.js">
</script>
<script>
dojo.require("dojo.io");
dojo.require("dojo.json");
dojo.require("dojo.widget.Form");
function createPhotoShare()
{
var userName = getUserName();
//
// create JSON objects to represent shared directory and blog
//
var directoryData = {
path = userName + '-photos',
contenttype = 'directory',
owner = userName
};
var blogData = {
DESCRIPTION : 'The photo blog for ' + userName,
AUTHOR: userName,
HANDLE: userName + '-photos',
TITLE: 'The photo blog for ' + userName
};
dojo.io.bind({
url: '/resources/file_shares/' + userName,
method: 'POST',
sync: false,
mimetype: 'text/json',
contentType: 'text/json',
postContent: dojo.json.serialize(directoryData),
load: function(type, data) {
alert("The new photo directory was created successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
dojo.io.bind({
url: '/resources/blogs',
method: 'POST',
sync: false,
mimetype: 'text/json',
contentType: 'text/json',
postContent: dojo.json.serialize(blogData),
load: function(type, data) {
alert("The new photo blog was created successfully.");
},
error: function(type, err) {
alert("Could not complete creation of photo share.");
deletePhotoShare();
}
});
}
function deletePhotoShare()
{
var user = getUserName();
dojo.io.bind({
url: '/resources/file_shares/' + user + '/' + user + '-photos',
method: 'POST',
sync: false,
headers: { 'X-Method-Override' : 'DELETE' },
load: function(type, data) {
alert("The photo directory was deleted successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
dojo.io.bind({
url: '/resources/blogs/' + user + '-photos',
method: 'POST',
sync: false,
headers: { 'X-Method-Override' : 'DELETE' },
load: function(type, data) {
alert("The photo blog was deleted successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
}
function getUserName()
{
return <%= _gc.get("/request/subject/remoteUser"); %>
}
</script>
...
<form dojoType="form" id="CreateAlbumForm" name="CreateAlbumForm">
<input type="button" onClick="createPhotoShare();" value="Create My Photo Share!" />
</form>
|
リスト 4 には理解しなければならないことがたくさんあるので、じっくり見てください。HTML フォームは、ユーザーが自分の名前で写真共有アカウントを作成するときにクリックするだけのボタンに過ぎません。デフォルトの振る舞いでは、ユーザーのアカウント (および関連するユーザーのファイル共有とブログ) には「ユーザー」-「写真」 (「ユーザー」はユーザーのログイン ID) というパターンで名前が付けられます。このアカウント名を使用して、createPhotoShare() 関数の HTTP POST リクエスト、deletePhotoShare() 関数の HTTP DELETE リクエストを構成します。この 2 つの関数で、dojo.io.bind() を使用して HTTP リクエストのデータの入力、そしてレスポンスの処理を行います。このコードで特に興味深いのは、前述のトランザクション管理と、新しい共有ディレクトリーおよびブログを表現する JSON オブジェクトの作成という 2 つの点で、いずれも createPhotoShares() の中で行われます。後者に関しては、JSON オブジェクトをコンポーネントで扱えるためにに必要なフィールドについての知識が必要です。この情報は、projectzero.org に記載されているコンポーネントの API 資料で調べてください (「参考文献」を参照)。
表 4 のもう 1 つのマルチ・リクエスト・アクションに必要なリクエストはリスト 4 のリクエストとは多少違って見えますが、構造自体は変わりません。つまり、すでに行われた処理を取り消すエラー・ハンドラーで dojo.io.bind() を複数回呼び出すという構造です。唯一異なる点は、写真またはアカウントの削除に関連付けられたアクションです。この場合、アカウントのデータを一部削除した後、たとえ部分的に失敗するとしてもタスクを完了させなくてはならなくなります。この問題は、すべての削除タスクをサーバー・サイド・スクリプト (実際のデータベース・トランザクションを実行可能) で実行すれば回避できますが、このような最適化に関しては、この記事では説明しません。
ここでもう一度、ブログおよび共有ディレクトリーだけで構成されているバックエンドに対して写真中心の名前と値を使っていることに注目してください。この写真共有 Web サイトにアクセスしたユーザーは、HTML ソース・コードを表示しない限り、ブログ・コンポーネントまたはファイル共有コンポーネントへの参照を一切目にすることがありません。そのため、このアプリケーションは表面下ではコンポーネントが分かれているにも関わらず、1 つのコンポーネントで構成されているかのように見えます。さらに、この 2 つのコンポーネントは写真共有以外のことにも役立つことから、アプリケーションは当初の計画より一層強力なものとなっています。これらのコンポーネントはすでにインストール済みであるため、「通常の」ブログ、それにユーザーが写真以外のファイルを共有する目的に使えないという理由はありません。1 つの Web サイトで、さまざまな目的に対応することができるのです。
まとめ
この記事では、リッチで強力なインターネット・アプリケーションを実現するために、複数の Zero コンポーネントをそれぞれの RESTful なインターフェースを使って結合する方法を説明しました。サービス指向アーキテクチャーと RESTful なリソースを基礎としたこの写真共有アプリケーションの取り組みは、可能な限りコードを再利用した実装につながり、カスタム・コードの大部分がユーザー・インターフェースにそのまま使われる結果となりました。REST 自体と同じく、この写真共有アプリケーションは、具体的なコードのセットというよりは、むしろアーキテクチャーのスタイルに近いものです。他のタイプのアプリケーションを Zero で構築する開発者は、実装を計画する際に Zero に提供させるコンポーネントを調査検討してください。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Dan Jemiolo は、ノースカロライナ州リサーチ・トライアングル・パークにある IBM Autonomic Computing チームの Advisory Software Engineer です。彼は Apache Muse 2.0 の設計および開発を指揮し、現在も引き続きこのプロジェクトに取り組んでいます。また、Dan は WS-RF TC に WS-ResourceMetadata 仕様のエディターとして参加しており、Web サービス標準の採用を促進する IBM の戦略に携わっています。彼はわずか 2 年前に、Rensselaer Polytechnic Institute で Computer Science を専攻し理学修士の学位を取得した後、IBM に入社しました。 |
記事の評価
|