Dojo のデータ抽象化レイヤーである dojo.data は、Dojo アプリケーションから多様なバックエンド・サービスにアクセスするための標準的な API です。Dojo の拡張機能ライブラリー DojoX の中にある JsonRestStore を使用すると、バックエンドの REST (Representational State Transfer) サービスにアプリケーションを簡単に接続することができます。
JsonRestStore は Dojo を REST に接続するための適切なソリューションですが、JsonRestStore では、サービスに対して渡す情報、またサービスから返される情報に対するデフォルトの想定フォーマットは決まっています。これらのやり取りをカスタマイズするための SMD (Service Mapping Description) 機能は、決して理解しやすいものではありません。
Dojo アプリケーションを作成する際に既存のバックエンド REST ストアと通信する必要がある場合、あるいはデータ・ストアの通信を変更することができない場合には、カスタムの dojox.rpc.Service 実装を使います。
この記事では、標準的ではない REST サービスを JsonRestStore に接続するための、単純で汎用的なサービスを実装する方法について学びます。実例を使って手順を示しながら、この実装を皆さんが提供しているサービスに対して使用する方法と拡張する方法について説明します。
この記事のソース・コードをダウンロードする方法については、「ダウンロード」セクションを参照してください。
EasyRestService には、4 つの REST 操作 (POST (作成)、GET (読み取り)、PUT (更新)、DELETE (削除)) に対する実装が用意されており、それぞれのアクティビティーごとに、サービス・メソッドの呼び出しの前後にフックできる機能があります。呼び出しのライフサイクルは以下のとおりです。
- Dojo で XHR を呼び出すための標準的な構造を作成します (この構造には、URL、引数、データ・フォーマットのメタデータが含まれています)。
- メソッドの引数を変換する関数を呼び出します。このメソッドによって呼び出しの構造を (パスを含めて) 変更し、またこのメソッドを使用することで、呼び出しの構造を REST サービスで想定される構造と同じになるように変換します。
- コンテンツ・ノードを JSON 表現に変換することにより、呼び出しの構造の putData/postData/deleteData/getData ノードにデータを追加します。
- XHR を呼び出すための変更された構造を使って XHR メソッドを呼び出します。
- XHR コールバックに呼び出し結果の変換関数を追加します。このメソッドにより、呼び出し結果のデータ構造を変更することができます。このメソッドを使用して、JsonRestStore で想定される構造に結果を変換します。
リスト 1 に EasyRestService のコードを示します。
リスト 1. EasyRestService のソース・コード
dojo.provide("com.ibm.developerworks.EasyRestService");
(function() {
var pa = com.ibm.developerworks.EasyRestService = function (path, serviceImpl, schema) {
// Enforce the dojox.rpc.Rest trailing slash functionality
path = path.match(/\/$/) ? path : (path + '/');
// A dojox.rpc.Service implementation is a function with 3 function members
var service;
// GET function
service = function(id, args) {
return _execXhr("get", id, args);
};
// POST function member
service['post'] = function(id, value) {
return _execXhr("post", id, value);
};
// PUT function member
service['put'] = function(id, value) {
return _execXhr("put", id, value);
};
// DELETE function member
service['delete'] = function(id) {
return _execXhr("delete", id);
};
// Generic XHR function for all methods
var _execXhr = function(method, id, content) {
// Transform the method string
var methodCapitalised = method.substring(0,1).toUpperCase()
+ method.substring(1).toLowerCase();
var methodUpperCase = method.toUpperCase();
var methodLowerCase = method.toLowerCase();
// Get the transformer functions
var argumentsTransformer = service["transform" + methodCapitalised + "Arguments"];
var resultTransformer = service["transform" + methodCapitalised + "Results"];
// Construct the standard query
var serviceArgs = {
url : path + (dojo.isObject(id) ? '?' + dojo.objectToQuery(id) :
(id == null ? "" : id)),
handleAs : "json",
contentType : "application/json",
sync : false,
headers : { Accept : "application/json,application/javascript" }
};
// Transform the arguments
// NOTE: argumentsTransformer has a reference to "service"
serviceArgs = argumentsTransformer(serviceArgs, arguments);
// Copy the content into the appropriate *Data arg
// getData, putData, postData, deleteData
// NOTE: If you want your arguments transformer to edit the *Data arg directly,
// move the arguments transformer invocation to after this call
serviceArgs[methodLowerCase + 'Data'] = content;
// Kick off the call
var xhrFunction = dojo['xhr' + methodCapitalised];
var deferred = xhrFunction(serviceArgs);
// Add our result transformer
// NOTE: resultTransformer has a reference to "service" too
deferred.addCallback(dojo.partial(resultTransformer, deferred));
return deferred;
};
// Mix in the service hooks
// Uses a "default" implementation that does nothing
// Service hooks will have a reference to the "service" object in their context
dojo.mixin(service,
new com.ibm.developerworks.EasyRestService.DefaultHooks(),
serviceImpl);
// Now remove any default _constructor() methods
// This is necessary as the JsonRestStore stack uses _constructor() differently
delete service['_constructor'];
// Remove the declaredClass member if it has been added
delete service['declaredClass'];
// Save the path away
service.servicePath = path;
// Save the schema
service._schema = schema;
return service;
};
})();
dojo.declare("com.ibm.developerworks.EasyRestService.DefaultHooks", null, {
transformGetArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformPutArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformPostArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformDeleteArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformGetResults: function(deferred, results) {
/*
* JsonRestStore expects the following format:
* [
* { id: "1", ... },
* { id: "2", ... },
* ...
* ]
*/
return results;
},
transformPutResults: function(deferred, results) {
/*
* JsonRestStore does not expect any specific content here
*/
return results;
},
transformPostResults: function(deferred, results) {
/*
* JsonRestStore expects:
* 1) A "Location" response header with location of the new item.
* From the Dojo API:
* The server’s response includes a Location header
* that indicates the id of the newly created object.
* This id will be used for subsequent PUT and DELETE
* requests. JsonRestStore also includes a
* Content-Location header that indicates the temporary
* randomly generated id used by client, and this
* location is used for subsequent PUT/DELETEs if no
* Location header is provided by the server or if
* a modification is sent prior to receiving a response
* from the server.
* NB: There is no JS method for altering response headers.
* You might wish to try overriding the
* deferred.ioArgs.xhr.getResponseHeader() method with your
* own implementation.
* 2) The new item in the following format:
* { id: "1", ... }
*/
return results;
},
transformDeleteResults: function(deferred, results) {
/*
* JsonRestStore does not expect any specific content here
*/
return results;
}
});
|
リスト 1 のコードは dojox.rpc.Rest のデフォルト関数をコピーしたものです。このコードを JsonRestStore に使用することができます (リスト 2)。
リスト 2. デフォルトで JsonRestStore に
EasyRestService を使用する
dojo.require("com.ibm.developerworks.EasyRestService");
dojo.require("dojox.data.JsonRestStore");
var store = new dojox.data.JsonRestStore({
service: new com.ibm.developerworks.EasyRestService("https://mydomain.com/restservice"),
idAttribute : "id"
}); |
デフォルトで、EasyRestService インスタンスは呼び出しの引数と、呼び出し結果をまったく変更しません。EasyRestService は、引数と結果に対する必要な変換を行うために DefaultHooks を変更するのではなく、インスタンス単位でこれらの変換処理を変更するためのメカニズムを提供します。
EasyRestService にはカスタム変換のための単純なメカニズムがあります。リスト 3 の例では、EasyRestService の動作を変更し、実行前に GET の呼び出しの構造をログに記録するようにしています。
リスト 3.
EasyRestService をカスタマイズする
dojo.require("com.ibm.developerworks.EasyRestService");
var transformers = {
transformGetArguments: function(args) {
console.log(args);
return args;
}
};
var service = new com.ibm.developerworks.EasyRestService(
"https://mydomain.com/restservice", transformers);
|
同様に、DefaultHooks での変換処理はすべて、インスタンス単位で変更することができます。
ここでは EasyRestService の 2 つのインスタンスを作成します。1 つのインスタンスは REST に準拠したサービスであり、もう 1 つは REST に準拠していないサービスです。この例では、この 2 つのインスタンスを JsonRestStore の 2 つのインスタンスに対するサービス・プロバイダーとして使用し、データ・ストアに対する基本的なフェッチを実行します。
リスト 4 とリスト 5 では、JSON 構造を含む 2 つの読み取り専用ファイルを使ってサービスを作成しています。
リスト 4. REST に準拠するサービス、compliantService.json
[
{ id: 1, name: "Phil" },
{ id: 2, name: "John" }
] |
リスト 5. REST に準拠しないサービス、noncompliantService.json
{ items : [
{ id: 1, name: "Phil" },
{ id: 2, name: "John" }
] } |
リスト 6 の JavaScript コードにより、データ・ストアをインスタンス化し、データ・ストアに対してクエリーを実行します。
リスト 6. データ・ストアとやり取りするための JavaScript コード
// Create a store using a service that needs no transformations
compliantStore = new dojox.data.JsonRestStore({
service : new com.ibm.developerworks.EasyRestService(
"./compliantService.json"),
idAttribute : "id"
});
// Cause an async fetch from the compliant service
dojo.create("p", {
innerHTML : "Requesting from compliant service"
}, dojo.body(), "last");
compliantStore.fetch({
onComplete : function(items, request) {
console.log(items);
// Log the number of items fetched
dojo.create("p", {
innerHTML : "Got " + items.length + " items from compliant service."
}, dojo.body(), "last");
}
});
// Create a store using a service which needs transformations
// to interpret the results
noncompliantStore = new dojox.data.JsonRestStore({
service : new com.ibm.developerworks.EasyRestService(
"./noncompliantService.json", {
transformGetResults : function(deferred, results) {
// This store wraps its results in an items object
// so return the items object
return results.items;
}
}),
idAttribute : "id"
});
// Cause an async fetch from the noncompliant service
dojo.create("p", {
innerHTML : "Requesting from noncompliant service"
}, dojo.body(), "last");
noncompliantStore.fetch({
onComplete : function(items, request) {
console.log(items);
// Log the number of items fetched
dojo.create("p", {
innerHTML : "Got " + items.length
+ " items from noncompliant service."
}, dojo.body(), "last");
}
}); |
この記事では、独自の REST サービスを JsonRestStore に接続する方法を学びました。取り上げた例では、JsonRestStore で要求されるシグニチャーを提供するためにサービスのインターフェースを変換する単純な方法を示しました。JsonRestStore で想定されるデータ構造についての完全な説明は、DefaultHooks についてのコメント、DojoCampus に用意された JsonRestStore のドキュメント、API のドキュメントなどを参照してください。
JsonRestStore で想定されるデータ構造についての完全な説明は、「参考文献」セクションを参照してください。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| EasyRestService code sample - project archive | EasyRestServiceExample.zip | 6.5KB | HTTP |
学ぶために
- DojoCampus で JsonRestStore のドキュメントを読んでください。
- JsonRestStore に対する Dojo の API について調べてください。JsonRestStore により、保存されたすべての変更を REST コマンド (GET、PUT、POST、DELETE) を使ってサーバーに送信することができます。
製品や技術を入手するために
- IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
- 今すぐ My developerWorks プロフィールを作成し、Dojo と REST に関するウォッチ・リストを設定してください。My developerWorks とずっとつながっていられます。
- Web 開発に関心を持つ他の developerWorks メンバーを見つけてください。
- Web の話題に焦点を絞った developerWorks のグループの 1 つに参加し、皆さんの知識を共有してください。
- Roland Barcia が彼のブログの中で Web 2.0 とミドルウェアについて語っています。
- developerWorks のメンバーが Web のトピックに関して共有するブックマークを調べてみてください。
- 即座に答えを得るために、Web 2.0 Apps フォーラムを訪れてください。

Nick Maynard はイギリスの Hursley にある IBM Software Solutions Transformation チームで働いています。彼の専門は、Web プログラミング、Linux、Web サービス、ビジネス統合技術などです。彼の連絡先は nick.maynard@uk.ibm.com です。