レベル: 中級 Adriaan de Jonge (adriaandejonge@gmail.com), Senior Java Software Engineer, SDB Java
2008年 2月 12日 SOA (Service Oriented Architecture) は通常、大企業のための重量級の技術を意味します。しかし SOA によるアーキテクチャー・パターンの利点は小規模な環境にも当てはまります。SOA の原則に従うためには、大規模な環境では便利なオーバーヘッドが必ずしもすべて必要なわけではありません。REST のように軽量な原則を使って SOA を利用することもできるのです。この記事ではその方法を説明します。
SOA とは何か
SOA (Service Oriented Architecture) は、さまざまな部門でさまざまな責任を持つ従業員達のためのアプリケーションを大量に有する企業で使われています。こうしたアプリケーションの多くは機能を共有しますが、機能の組み合わせやユーザー・インターフェースの詳細、またユーザビリティーに対する要件などは異なります。多くのエンタープライズ・アーキテクチャーと同様、SOA はマルチティアのモデルに従っていますが、単にそれだけのものではありません。サーバーの中では、機能は別々のサービスに分割されており、クライアントは 1 つまたは複数のサービスを利用することができ、また 1 つのサービスを多くのクライアントが利用することもできます。その結果、既存のソフトウェアを再利用しやすい、疎結合のアーキテクチャーが作られます。
通常の大規模な実装
 |
頻繁に登場する頭字語
- API: Application Program interface
- IT: Information Technology
- XML: Extensible Markup Language
|
|
SOA が特に適しているのは、数百というアプリケーションが不適切に統合され、また IT インフラの整理が必要な大企業です。SOA は実証されたプラクティスであり、大規模な環境の中で効果的に機能することができます。アダプターを使用することによって、レガシー・アプリケーションをサービスに変換し、そうしたサービスをバックエンドとして最新のアプリケーションに統合することができます。また、サービスを協調させ、サービスの特定の機能へのアクセスを制御するためのミドルウェア技術も提供されています。SOA への要求が最も高いのはこの領域であるため、ミドルウェア技術のベンダーは通常、製品の焦点を大規模なソリューションに絞ります。
SOA と軽量技術
SOA の背景にある考え方は、小規模な企業にとっても貴重なものです。大規模なソリューションにかかるセットアップ・コストや必要とされる知識の問題から、小企業は SOA を試そうとしないのかもしれませんが、そうであってはいけません。いったん大規模なソリューションの実装については忘れ、次のような SOA の基本的な概念について考えてみてください。
- 既存の、あるいは新しいアプリケーションからサービスを抽出する
- 多くのクライアントが利用できるようにサービスを一元化する
この概念を軽量技術で実装できないはずはありません。小さなことから始め、それを育てればよいのです。皆さんの企業が大規模な多国籍企業へと成長したら、後からいつでも重量級の技術に移行することができます。
REST とは何か
通常、SOA は、WSDL (Web Services Description Language) 文書によって記述される SOAP プロトコルを使って実装されます。多くの開発ツールのおかげで SOAP と WSDL を扱うのは比較的容易ですが、そうしたツールを使わないと SOAP と WSDL を扱うのは困難なため、私は両者を重量級の技術と見なしています。
SOA は、単純なメッセージを HTTP (Hypertext Transfer Protocol) を使って送信する方法でも適切に実装することができます。基本的に、それこそまさに RESTful な Web サービスが行っていることです。REST、つまり Representational State Transfer は (REST という名前は Roy Fielding が考えたものです)、プロトコルや技術ではなく、アーキテクチャーのスタイルです。また REST は SOAP に代わる軽量な技術であり、アクション指向ではなくリソース指向です。REST はよく、HTTP を使った GET、POST、PUT、そして DELETE の実行をリモート・プロシージャー・コールによって行うもの、と簡単に説明されます。しかし、私の考えでは、これは 2 番目に重要なステップです。
第 1 の、そして最も重要なステップは、すべてのリソースを URL としてモデリングすることです。URL は、覚えやすいという単純さと、何十億もある Web ページに到達できる機能とを併せ持っています。少なくとも、適切にモデリングすれば URL は覚えやすいものであり (例えば http://www.ibm.com/developerworks/xml/ など)、GET、POST、PUT、DELETE に集中しすぎて http://www.longfakeurl.com/pol_srdm/70612/9,3993.32?id=78688&lang=cz&st=idx のように直感的でない、忘れてしまうような URL にすることがなければよいのです。
HTTP を使うといっても、実際にはさらに GET と POST に絞られます。これは、主なブラウザーで最もよくサポートされているのが GET と POST だからです。つまり http://domain.com/myresources に PUT を使う代わりに http://domain.com/myresources/new に POST を使い、http://domain.com/myresources/oldresource に DELETE を使う代わりに http://domain.com/myresources/oldresource/delete に POST を使うのです。
RESTful な設計手順
RESTful な Web サービスを設計するためのガイドラインとして、以下の 4 つのステップに従います。
- リソースと、そのリソースを表す URL を決定します。
- 各 URL に対して通信するためのデータ・フォーマットを選択します。
- 各リソースに対するメソッドを指定します。
- 返されるデータとステータス・コードを指定します。
以下はその方法です。仮に、皆さんが航空会社で働く開発者だと考えてみてください。この航空会社はフライトを予約するためのソフトウェアと、支払い (現金やクレジット・カード) を処理するためのコンポーネントを持っています。この会社はソフトウェアを使って手荷物を追跡し、内部でのリソース計画を行い、そして他の多数のタスクを実行します。
チェックイン・カウンターの従業員がクライアント・アプリケーションを使うと考えてみてください。このアプリケーションは手荷物追跡サービスにアクセスしたり、別のサービスを使って乗客に座席を割り当てたりします。地上で手荷物を扱う人達は手荷物追跡サービスのみを必要とし、他のサービスは必要ありません。彼らはクライアントを使用する際、既にチェックインされた手荷物の到着を確認することしか許可されておらず、新しい手荷物をチェックインすることはできません。
この例では、手荷物追跡サービスを設計します。まず、リソースを決定します。リソースは旅行者、便名、そして手荷物です (あちこちに {id} があることに注目してください。{id} には任意のランダムな数字が入ると考えてください)。
http://luggagetracking.airlinecompany.com/bags/{id}
http://luggagetracking.airlinecompany.com/flights/{id}
http://luggagetracking.airlinecompany.com/travellers/{id}
|
各リソースに対するデータ・フォーマットを選択します。
Bag (手荷物):
<bag id="{id}">
<traveller id="{traveller-id}"/>
<flight id="{flight-id}" />
<status>{current-status: departure/plane/arrival}</status>
</bag>
|
Flight (便名):
<flight id="{id}">
<travellers>
<traveller id="{traveller-id-0}" />
<traveller id="{traveller-id-1}" />
<traveller id="{traveller-id-2}" />
</travellers>
<bags>
<bag id="{bag-id-0}" />
<bag id="{bag-id-1}" />
<bag id="{bag-id-2}" />
</bags>
</flight>
|
Traveller (旅行者):
<traveller id="{id}">
<flight id="{flight-id}" />
<bags>
<bag id="{bag-id-0}" />
<bag id="{bag-id-1}" />
<bag id="{bag-id-2}" />
</bags>
</traveller>
|
当然ですが、このモデルは非常に単純なものです。この例では 2 つのメソッドがサポートできればよく、そのためにはこのモデルで十分です。チェックイン・カウンターでは、旅行者のために新しい手荷物をチェックインできる必要があります。また地上職員は、手荷物を機内に積み込んだら手荷物のステータスを変更できる必要があります。
-
http://luggagetrackingairlinecompany.com/travellers/{id}/newbag に
POST リクエストを送信すると、<bag> という XML 構造が返されます。
-
http://luggagetracking.airlinecompany.com/bags/{id}/status/{newstatus} に
POST リクエストを送信すると、変更された XML 構造が返されます。
ステータス・コードに関しては標準の HTTP ステータスを使います。成功したアクションはすべて 200 を返します。もしシステムが ID に対応したリソースを発見することに失敗すると、システムは 404 を返します。システムの障害によるエラーの場合には 500 が返されます。
サンプル・コード: URL マッピング
実装メソッドに URL をマッピングするための選択肢はたくさんあります。より高度なメソッドは、より柔軟であり、大規模なアプリケーションで最高のパフォーマンスを発揮するはずです。この小さな例では、動作するものの中で最も単純な方法である、正規表現を使います。以下に示すのは BagServlet の POST メソッドの例であり、ベースとなるサーブレットに URL 引数を渡します。完全なサーブレットは、この記事から入手できるダウンロード・ファイルの中にあります。そのサーブレットでは実際に基礎となるサービスは実装されていないことに注意してください。以下に示すのはこのメソッドの例です。
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Pattern pattern = Pattern.compile("^/?.*?/bags/(.*)/status/(.*)$");
Matcher matcher = pattern.matcher(request.getRequestURI());
if(matcher.matches()) {
String bagId = matcher.group(1);
String newStatus = matcher.group(2);
bagService.changeBagStatus(bagId, newStatus);
}
}
|
この URL は呼び出されると、成功したときには暗黙的にステータス・コード 200 を返します。もっと興味深いことは、このコードが XML 構造を返すという事実です。この例は XStream API を使って Java™ オブジェクトを XML 構造にシリアライズしています。この API は最小限の構成しか必要とせず、また要素名の選択はほとんどクラスのフィールド名に依存しています。
このサンプル・コードでは、デモ用に以下の単純なクラスを使っています。
Flight:
package eu.adraandejonge.restfulsoa;
public class Flight {
String id;
public Flight(String id) {
super();
this.id = id;
}
}
|
Traveller:
package eu.adraandejonge.restfulsoa;
public class Traveller {
private String id;
public Traveller(String id) {
super();
this.id = id;
}
}
|
Bag:
package eu.adraandejonge.restfulsoa;
public class Bag {
private String id;
private Flight flight;
private Traveller traveller;
private String status;
public Bag(String id, Flight flight, Traveller traveller, String status) {
super();
this.id = id;
this.flight = flight;
this.traveller = traveller;
this.status = status;
}
}
|
例えば、ベースとなる bagService が flight ID = 1、traveller ID = 1、status = new という bag を返すとした場合、以下の GET の実装を考えてみてください。
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Pattern pattern = Pattern.compile("^/?.*?/bags/(.*)$");
Matcher matcher = pattern.matcher(request.getRequestURI());
if (matcher.matches()) {
String bagId = matcher.group(1);
Bag bag = bagService.retrieveBag(bagId);
XStream xstream = new XStream();
xstream.alias("bag", Bag.class);
xstream.alias("traveller", Traveller.class);
xstream.alias("flight", Flight.class);
xstream.useAttributeFor(Bag.class, "id");
xstream.useAttributeFor(Traveller.class, "id");
xstream.useAttributeFor(Flight.class, "id");
String xml = xstream.toXML(bag);
response.getWriter().write(xml);
}
}
|
この URL を照会すると、以下の内容が返されます。
<bag id="1">
<flight id="1"/>
<traveller id="1"/>
<status>new</status>
</bag>
|
この先は
ここでは、ベースにある通信をあまり行わずに URL の強力さを示すためのサンプル・コードを選択しました。他のサービスの場合には、REST サービスにアップロードされた XML 構造を処理する必要があるかもしれません。その場合にも XStream が役立ちます。例えば、bag をデシリアライズするためには以下のように XStream を呼び出せばよいのです。
Bag bag = (Bag) xstream.fromXML(xml);
|
クライアントとアプリケーション
この記事ではここまで、サーバー・サイドでの実装について説明してきました。クライアント・サイドで実装されるコードも非常に似ています。データ・クラス Flight、Traveller、Bag をクライアントと共有し、XStream API を使って XML をシリアライズ、デシリアライズするのです。クライアント・サイドで唯一新しい部分は、URL に接続してコンテンツを読み取ったりコンテンツを投稿したりする部分です。URL への接続は Java のクラス・ライブラリーを利用すると以下のように容易に行うことができます。
String xml = "<newinput>input</newinput>";
URL url = new URL("http://luggagetracking.airlinecompany.com/bags/1/newmethod");
URLConnection connection = url.openConnection();
// set POST
connection.setDoOutput(true);
Writer output = new OutputStreamWriter(connectiongetOutputStream());
output.write(xml);
output.close();
// display result
BufferedReader input = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String decodedString;
while ((decodedString = input.readLine()) != null) {
System.out.println(decodedString);
}
input.close();
|
Ruby on Rails のような技術との相互運用性
REST の実装方法に関する正確な仕様はありませんが、何も手を加えずそのままの状態で REST をサポートするものが増えています。標準に従う代わりに、いくつかの慣例に従う必要があります。例えば Ruby on Rails は ActiveResource を提供しています。URL と出力フォーマットに関する Rails の慣例に従えば、最小限のオーバーヘッドで Rails の Web クライアントを Java の RESTful な Web サービスに容易に接続することができます。
スケーラビリティーと、大規模な SOA への移行
アプリケーションの環境が拡大するにつれ、REST 実装の詳細がますます抽象化されることでしょう。アプリケーション環境の拡大と REST 実装の抽象化が一定レベルに達すると、当初の軽量な技術を使うよりも大掛かりな仕組みを使った方が安価になる可能性があります。
サービスの背後にある実際のビジネス・ロジックを抽出し、それを新しい環境で SOAP パッケージのに再度ラップすることは、それほど困難ではないはずです。
RESTful な SOA のための独自アプリケーションを発見する
ここで取り上げた航空会社の例は、この記事を説明するための単なる例にすぎません。実際の航空会社は即座に重量級の技術を採用するはずです。もし皆さんが小規模な会社で働いている場合には、実際にどうすれば SOA と REST の原則を最高に利用できるかをイメージするために、少し想像力が必要かもしれません。少し時間をかけて考えてみてください。長い目で見れば、元は十分に取れるはずです。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample code of servlet | x-restfulsoa.zip | 2657KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Adriaan de Jonge はオランダのハーグにある SDB Professionals の Java 専門家チームの一員です。ライターとしての彼の経歴は、XForms と Ruby on Rails の比較で始まりました。彼は Java™ 開発者として、Web ベースとクライアント・サイド両方のフロントエンド技術に特に関心を持っています。連絡先は adriaandejonge@gmail.com です。 |
記事の評価
|