本文へジャンプ

WebSphere Developer Domain > WebSphere Business Integration > 

WebSphere Process ServerとWebSphere ESBとの接続でRESTを使用する方法

レベル: 中級者向け

Greg Flurry (flurry@us.ibm.com), Enterprise Integration Solutions Strategist, IBM
2006年11月08日, 原文はこちら(US)

インデックス
はじめに
WebSphere Process ServerとWebSphere ESBでのREST流の対話動作
実装例
インバウンド・アダプター
まとめ
参考文献
著者について


はじめに

REST(Representational State Transfer)という言葉は、システム間の対話動作のアーキテクチャー・スタイルを定義する幅広い意味を持っています。RESTは、厳密なルールやしっかりと定義されたAPIを備えた標準ではありませんが、インターネットの世界では、RESTを使った対話動作(以下では「REST流の対話動作」と呼ぶことにします)が広く行われています。RESTの方式を的確に説明した記事として、「Building Web services the REST way (US)」を参照してください。

IBM® WebSphere® Process ServerとIBM WebSphere Enterprise Service Bus(ESB)は、SCA(Service Component Architecture)標準とSDO(Service Data Object)標準を用いて構築されています。SCAとSDOによって、対話動作を行うSCAのコンポーネントやモジュール同士の接続を、実装技術に依存せずに行うことができます。SCAでは、対話動作を行うモジュール間の物理的な対話メカニズムをバインディングが定義します。そして WebSphere Process Server V6.0.2と WebSphere ESB, V6.0.2は、Webサービスや JMS、アダプターのためのバインディング(EISバインディングと呼ばれます)を提供しています。またこれら2つの製品は、デフォルトのバインディング(SCAバインディング)も提供しています。しかしこれらのバインディングは、どれもREST流の対話動作を行うことはできません。

この記事では、この2つのWebSphere製品でREST流の対話動作を実現するために考慮すべき事項と実現するための選択肢のいくつかについて解説します。その後で、上記の参照記事で説明されている対話動作の原則に従う実装の例を示すことにします。

WebSphere Process ServerとWebSphere ESBでのREST流の対話動作

REST流の対話動作は通信トランスポートのベースとして HTTPを使い、また対話動作のための構文としてURLとXML文書を使います。上記の参照記事では、HTTPリクエストが「論理」URLを使うこと、またリクエスターからプロバイダーへの情報転送で送信される文書や、プロバイダーからリクエスターへの情報転送で受信される文書などについて解説しています。それぞれのリソースには、1つのURLがあり、そのURLは、単純な「名詞」です。対話動作の「動詞」は、HTTPメソッド(GET、または POST、PUT、DELETE)によって定義されます。上記の参照記事では、この対話動作が、HTTPヘッダーなどの「アウト・オブ・バンド」情報を必要とせずに対話動作の「イン・バンド」(ビジネス)動作を定義できることも解説しています。

SCAモジュールは、古典的なオブジェクト指向プログラミング環境から派生した、非常に異なる構文を持っています。そのためSCAインターフェースには「動詞」に相当する操作や、「リソース」に相当する要求や応答パラメーターなどがあります。

WebSphere Process ServerとWebSphere ESBの場合、REST流のリクエスターまたはプロバイダーがビジネスまたはメディエーション用のSCAモジュールと対話動作するためには、一般的な意味での「アダプター」が必要です。下記の図1に示すように、アウトバウンド・アダプターは、SCAモジュールからリクエストを受け付け、そのリクエストをREST流のHTTPによってREST流のプロバイダーに送信し、REST流のHTTPレスポンスをプロバイダーから受信し、そのレスポンスをSCAモジュールに返します。インバウンド・アダプターは、REST流のHTTPリクエストを受け付け、そのリクエストを SCAモジュールに送信し、SCAモジュールからレスポンスを受信し、そしてREST流のHTTPレスポンスをリクエスターに返します。



図1 SCAモジュールのためのアダプター

アウトバウンド・アダプター

まず、アウトバウンド・アダプターについて考えましょう。目標としては、SCAモジュールが、定義された「インターフェース」を使って既存のREST流の「サービス」と対話動作できるようにすることです。このインターフェースを、まずSCAに準拠したインターフェースにマップする必要があります。

図1のようなアダプター最も単純に設計するための方法を、下記の図2に示します。このアダプター・モジュールは、他のどのSCAモジュールからも独立しています。そしてこのアダプター・モジュールは、(他のSCAモジュールが適切に使用できるような)SCAインターフェースとなるエクスポートを提供する必要があり、そしてもちろん、REST流のHTTPリクエストを送信でき、REST流のHTTPレスポンスを受信できる必要があります。このアダプター・モジュールのアダプター・コンポーネントは、HTTPの対話動作を実行します。この設計で重要なことは、アダプター・コンポーネントがモジュール内の他のコンポーネントと同じスレッドで実行すること、従って良いパフォーマンスが得られる点です。アダプター・モジュールの中のエクスポートは、アダプター・コンポーネントと同じインターフェースを持つ場合もあり、持たない場合もあります。これについては後ほど詳しく説明します。



図2 アダプター・モジュール

アダプターの実装の選択肢としては、SCAモジュール(ビジネスまたはメディエーションのいずれか)のタイプにあるJava™ コンポーネント、あるいは、メディエーション・モジュール内のメディエーション・フロー・コンポーネントの中にあるメディエーション・プリミティブが適切です。図3は、ビジネス・モジュール用の選択肢を示しています。左側の設計は、2つの状況を説明しています。第1の状況では、アダプター・コンポーネントは REST流のサービスにとってのナチュラル・インターフェースを定義しています(つまり、REST流のサービスの XML文書をパラメーターとして使います)。そしてエクスポートは同じインターフェースを提供しています。ナチュラル・インターフェースを使うように他のSCAモジュールを開発、修正できる場合には、この設計は適切な選択肢です。

第2の状況では、他のSCAモジュールを変更できない場合、必要なインターフェースをエクスポートが定義しています。この場合、ナチュラル・インターフェースの変換をアダプター・コンポーネントが行う必要があります。この設計は、マイナーな変換しか必要なければ適切な選択肢です。右側の設計では、アダプター・コンポーネントがREST流のサービスのナチュラル・インターフェースを提供し、エクスポートは他のSCAモジュールで要求される別のインターフェースを提供しています。Interface Map(IFM)コンポーネントは、必要な変換を行います。この設計は、アダプター・コンポーネントの実装を可能な限り汎用で、再利用可能なものにできるので、高度なインターフェース変換が必要な場合に適切な選択肢です。この2つの設計のいずれの場合も、アダプター・コンポーネントをバインディングの1つの形式と考えることができ、またモジュールをメディエーションの形式として、そして論理的にはESBの一部と考えることができます。



図3 ビジネス・モジュール用のアダプターの選択肢

ビジネス・モジュールが修正可能であり、またパフォーマンスが重要であるか、あるいはコンサーンの分離が重要ではない場合には、もう1つ選択肢があります。下記の図4は、ビジネス・モジュールの中に含まれてるアダプター・コンポーネントが最適化された形で適用されています。必要な変換はビジネス・コンポーネントの中で、あるいは別のInterface Mapコンポーネント(図には示してありません)の中で行われるため、図2の設計では必要であった、リモート・プロシージャー・コールをなくすことができます。



図4 もう 1 つのビジネス・モジュール用の選択肢

REST流のサービスとの対話動作には、ビジネスに関連しない「アウト・オブ・バンド」情報(IDトークンなど)が要求される場合があります。この記事ではセキュリティーに関しては説明しませんが、アウト・オブ・バンド情報を処理する際の選択肢をいくつかあげておきます。必要な情報は、J2EEセキュリティー・コンテキストのように環境の一部として存在することもあれば、SOAPヘッダーなど、メッセージのアウト・オブ・バンド部分として存在することもあります。後者の場合、図2に示した、アダプター・モジュールのためのビジネス・モジュール実装は使えず、従ってメディエーション・モジュールを使う必要があります(メディエーション・モジュールはメッセージの中のアウト・オブ・バンド情報にアクセスできます)。メディエーション・フロー・コンポーネントの目標の1つは、アウト・オブ・バンド情報を抽出し、その情報をアダプターが利用できるようにすることです。そうすれば、例えばSOAPリクエスト・ヘッダーの中で渡されるIDトークンを、HTTPクッキーとして渡すことができます。

下記の図5は、考えられる2つの設計方式を示しています。左側では、メディエーション・フロー・コンポーネント(MFC)はメッセージ全体にアクセスでき、そしてヘッダー情報を検証することができます。しかし、WebSphere Process Server V6.0.2とWebSphere ESB, V6.0.2 の場合、アダプター・コンポーネントはメッセージ全体にアクセスできません。この制約を回避する方法は2つあります。第1は、アダプター・コンポーネントが提供するインターフェースを、ビジネス・ロジックが必要とする以上に機能強化し、アウト・オブ・バンド情報を含むようにする方法です。例えば、<名前、値>の対の単純な配列を操作パラメーターに追加し、メディエーション・フロー・コンポーネントがヘッダーから情報を抽出してその配列に挿入する、という方法が可能です。第2は、メディエーション・フロー・コンポーネントがアウト・オブ・バンド情報をJavaのThreadLocalインスタンスに置く方法です。アダプター・コンポーネントは同じスレッドで実行するため、アウト・オブ・バンド情報にアクセスすることができます。どちらの方法の場合も、アダプター・コンポーネントがアウト・オブ・バンド情報を処理し、その情報を適切な形式で HTTPリクエストに挿入します。HTTPレスポンスに対しても、同様の手法が必要です。アダプター・コンポーネントは、返されたアウト・オブ・バンド情報 (set-cookieヘッダーなど)を、アダプター・コンポーネントを拡張した戻りパラメーターの中、あるいはThreadLocalインスタンスの中に置きます。そうするとメディエーション・フロー・コンポーネントはアウト・オブ・バンド情報にアクセスでき、その情報を、例えばSOAPレスポンス・ヘッダーの中に挿入することができます。



図5 メディエーション・モジュールの選択肢

図5の右側には、もう1つの方法が示されています。この場合、アダプターは、メディエーション・フロー・コンポーネント内部の、カスタム・メディエーション・プリミティブです。メディエーション・プリミティブはリクエストまたはレスポンスのヘッダー情報にフル・アクセスでき、また受信するリクエストのヘッダー情報に直接アクセスしてHTTPリクエストの中に挿入することができます。同様にメディエーション・プリミティブは、HTTPレスポンス・ヘッダーにアクセスでき、発信するレスポンス・ヘッダーの中に情報を直接挿入することができます。

インバウンド・アダプター

インバウンド・アダプターの目標は、REST流のサービスが既存のSCAモジュールと対話動作できるようにすることです。このアダプターを図1のように最も単純に実装する方法を、下記の図6に示します。このアダプター・モジュールは、HTTPリクエストを受信し、HTTPレスポンスを返します。また、他のSCAモジュールとの対話動作をサポートする、インポートを持つ必要があります。インバウンド・アダプテーションは、アウトバウンド・アダプターでのアクティブ・コンポーネントの代わりに、SCAスタンドアロン参照によってアダプター・モジュールと結合されたJ2EEサーブレットを用いて実装されます。この設計によって、サーブレットはSCAモジュールをローカル・コールでき、またリモート・プロシージャー・コールがなくなるためパフォーマンスを最大に高めることができます。サーブレットはHTTPによる対話動作を実装しますが、スタンドアロン参照で定義されるSCAインターフェースを呼び出します。アダプター・モジュールのインポートは、スタンドアロン参照と同じインターフェースを持つこともあれば、持たないこともあります。これについては後ほど詳しく説明します。



図6 インバウンド・アダプター

サーブレットとスタンドアロン参照の組み合わせを、ビジネスSCAモジュール、あるいはメディエーションSCAモジュールのいずれかで実装することができます。下記の図7は、ビジネス・モジュールで実装する場合を示しています。左側では、インポートがSCA環境のためのナチュラル・インターフェースを定義しています。サーブレットは、インポートが提供するインターフェースを使ってREST流のインターフェースにマップします。アウトバウンドの場合と同様、サーブレットは単純なマッピング以上の変換を行えますが、そうすると再利用性が低下するかもしれません。右側では、サーブレットはREST流のサービスのナチュラル・インターフェースを使用しています(例えば既にこのインターフェースが存在している場合など)。一方インポートは、SCAシステムに対する別のナチュラル・インターフェースを提供しています。Interface Mapコンポーネントは必要な変換を行います。この2つの設計のいずれの場合も、アダプター・コンポーネントをバインディングの1つの形式と考えることができ、またモジュールをメディエーションの形式として、そして論理的にはESBの一部と考えることができます。



図7 ビジネス・モジュール用のアダプターの選択肢

ビジネス・モジュールが修正可能であり、またパフォーマンスが重要であるか、あるいはコンサーンの分離が重要ではない場合には、もう1つ選択肢があります。下記の図8は最適化された形のアダプテーションを示しており、ビジネス・モジュールの中にサーブレットとスタンドアロン参照が含まれています。必要な変換は、サーブレットの中で、あるいは別のBusiness Interface Mapコンポーネント(図には示してありません)の中で行われます。



図8 もう1つのビジネス・モジュール用の選択肢

アウトバウンドの場合と同様、対話動作にアウト・オブ・バンド情報が使われる場合があります。例えばSCAモジュールが、J2EE環境の一部として、あるいはメッセージ・ヘッダーのアウト・オブ・バンドとして、何らかのIDを要求する場合があります。前者の場合、サーブレットが必要な情報をリクエストから抽出し、その情報を環境に挿入できるかもしれません。この場合、サーブレットが使用するインターフェースはインポートが使用するインターフェースと同じなので、図6の、アダプター・モジュールのためのビジネス・モジュール実装が使えます。メッセージ・ヘッダーのアウト・オブ・バンド情報の場合には、図6の、アダプター・モジュールのためのビジネス・モジュール実装は使えず、メディエーション・モジュールを使う必要があります。この場合のサーブレットの目標の1つは、アウト・オブ・バンド情報を抽出し、その情報をメディエーション・フロー・コンポーネントが利用できるようにすることです。そうすれば、例えばHTTPクッキーとして渡されるIDトークンをSOAPヘッダーの中に置くことができます。

下記の図9は、メディエーション・モジュールの実装を示しています。インバウンド・アダプターには、先ほど説明した、機能強化したインターフェースによる方法、あるいはThreadLocalによる方法を使うことができます。機能強化したインターフェースを使う場合、スタンドアロン参照は拡張されたインターフェースを持ち、またインポートはナチュラル・ビジネス・インターフェースを持っています。一方、ThreadLocalによる方法を使う場合には、サーブレットは、ThreadLocalにアウト・オブ・バンド情報を挿入するか、あるいはThreadLocalからアウト・オブ・バンド情報を取得します。メディエーション・フロー・コンポーネントのメディエーション・プリミティブは、サーブレットからの情報に基づいてアウトバウンド・リクエスト・ヘッダーを操作し、またインバウンド・レスポンス・ヘッダーを調べて情報をサーブレットに提供します。



図9 メディエーション・モジュールのオプション

上に戻る

実装例

このセクションでは、上で説明したアウトバウンド・アダプターとインバウンド・アダプターの実装について説明します。この例は、WebSphere Process ServerとWebSphere Integration Developerに慣れていることを前提にしています。

インバウンド・アダプター

ここで、REST流の世界が提供する既存のサービス、例えばショッピング・カート・サービスと対話動作を行いたいとしましょう。REST流のサービス・インターフェースは、下記の図10に定義されています。もちろん皆さんは、URLのデータ型やステータス・コード、カートやアイテムの内容についてのXMLスキーマを知っている必要があります。こうした詳細については下記で説明します。


図10 REST流のインターフェース
内容URL [HTTP メソッド]リクエスト文書レスポンス文書
カートの作成../cart [PUT]--カートのURL
カートの削除../cart/cartID [DELETE]--ステータス・コード
カートの取得../cart/cartID [GET]--カートの内容
アイテムの追加../cart/cartID [PUT]アイテムの内容アイテムのURL
アイテムの削除../cart/cartID/itemID [DELETE]--ステータス・コード

最初のステップは、SCA準拠のインターフェースに単純にマッピングします。


図11 SCA準拠のインターフェース
内容操作入力パラメーター出力パラメーター
カートの作成create()--カートのURL

カートの削除

delete()カートIDステータス・コード
カートの取得

retrieve()

カートIDカートの内容
アイテムの追加

addItem()

カートID、アイテムの内容アイテムのURL
アイテムの削除

deleteItem()

カートID、アイテムIDステータス・コード

次のステップは、データ型の詳細を理解し、データ型とインターフェースを保持するライブラリー・プロジェクトを作成することです。下記の図12は複合データ型を示しています。URLとステータス・コードはストリングです。下記の図13はMyShoppingCartインターフェースを説明しています。



図12 データ型



図13 MyShoppingCartのSCA準拠インターフェース

図3の左側に示したアダプターに関する次のステップは、アダプター・モジュールとして動作するビジネス・モジュールを作ることです。このモジュールは、エクスポートと、アダプター・コンポーネントであるJavaコンポーネントを含む必要があります。これを下記の図14に示しますが、この図は、このアダプター・モジュールを使用するビジネス・モジュールも示しています。この場合、インポートとエクスポートに使用されるバインディングはどんなものでもよく、この例ではデフォルトの(SCA)バインディングを使っています。




図14 ビジネス・モジュールとアダプター・モジュール

アダプター・モジュールを作成するためには、下記を行う必要があります。

  1. ビジネス・モジュールを作成します。依存関係エディタを使って、このモジュールがライブラリーを使えるようにします。
  2. このモジュールにJavaコンポーネントとエクスポートを追加し、適切な名前を付けます。
  3. Javaコンポーネント用のインターフェースをMyShoppingCartに設定します。
  4. エクスポートをアダプター(Java)コンポーネントに接続します。
  5. アダプター・コンポーネントの実装を生成します。
  6. アダプター・モジュールを保存し、テスト環境にデプロイします。

下記の図15は、アダプター・コンポーネントの実装を示しています。初期化期間中にBOXMLSerializerサービスが作成され、下記で説明するように、リクエストとレスポンスを(デ)シリアライズします。ビジネス・ロジック・メソッドの一般的な形式は下記のとおりです。

  1. 必要なURLを作成します。
  2. 入力を処理し、REST流のサービスを呼び出します。
  3. レスポンスを処理し、レスポンスを返します。ユーティリティー・メソッドを使ってサービスを呼び出し、レスポンスを返します。

図15 アウトバウンド・アダプター・コンポーネントのコード

package sca.component..impl;
            
public class AdapterComponentImpl {
            
    private String outboundURL = "http://../cart";
    private BOXMLSerializer xmlSerializerService = null;
            
    public AdapterComponentImpl() {
        super();
        xmlSerializerService = (BOXMLSerializer) new   
            ServiceManager().
            locateService("com/ibm/websphere/bo/BOXMLSerializer");
    }
            
    public String create() {
        // create URL
        StringBuffer urlString = new StringBuffer(outboundURL);
        // invoke RESTful serviceInputStream is = interactHTTP(urlString, "PUT", null);
        return getCode(is, true);
    }    
            
    public String addItem(String cartID, DataObject lineItem) {
        // create URL
        StringBuffer urlString = new StringBuffer(outboundURL);
        urlString.append("/" + cartID);
            
        // send line item
        ByteArrayOutputStream lineItemOut = 
            new ByteArrayOutputStream();    
        try {
            xmlSerializerService.writeDataObject(lineItem,
                "http://mystore.com", 
                "LineItem", lineItemOut);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // invoke RESTful service
        InputStream is = interactHTTP(urlString, 
            "PUT", lineItemOut);
        return getCode(is, true);
    }
            
    public String deleteItem(String cartID, String lineItemID) {
        // create URL
        StringBuffer urlString = new StringBuffer(outboundURL);
        urlString.append("/" + cartID+"/"+lineItemID);
        // invoke RESTful serviceInputStream is = interactHTTP(urlString, "DELETE", null);
        return getCode(is, false);
    }
            
    public DataObject retrieve(String cartID) {
        // create URL
        StringBuffer urlString = new StringBuffer(outboundURL);
        urlString.append("/" + cartID);
            
        DataObject parm = null;
        // invoke RESTful service
        InputStream is = interactHTTP(urlString, "GET", null);
            
        try {
            BOXMLDocument doc =
                xmlSerializerService.readXMLDocument(is);
            parm = doc.getDataObject();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return parm;
    }
            
    public String delete(String cartID) {
        // create URL
        StringBuffer urlString = new StringBuffer(outboundURL);
        urlString.append("/" + cartID);
        // invoke RESTful service
        InputStream is = interactHTTP(urlString,"DELETE", null);
        return getCode(is);
    }
            
    private InputStream interactHTTP(StringBuffer urlString,
        String method, ByteArrayOutputStream out) {            
        
        InputStream response = null;
            
        try {
            URL url = new URL(urlString.toString());
            URLConnection conn = url.openConnection();
            HttpURLConnection hConn = (HttpURLConnection) conn;
            hConn.setRequestMethod(method);
            
            if (out != null) {
                // send output
                conn.setDoOutput(true);
                OutputStream os = hConn.getOutputStream();
                os.write(out.toByteArray());
                os.close();
            }
            
            // get response
            response = conn.getInputStream();
            
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
        return response;
    }
            
    private String getCode(InputStream is) {
        String code = null;
        BufferedReader br =
            new BufferedReader(new InputStreamReader(is));
            
        try {
            code = br.readLine();
            br.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (code.lastIndexOf('/') > 0)
            code = code.substring(code.lastIndexOf('/')+1);
            
        return code;
    }
}

実装は比較的単純です。create()操作の場合、入力は何もなく、返されるレスポンスは単純なストリングです。delete()操作の場合、入力は削除対象のショッピング・カートのIDであり、返されるレスポンスは単純なストリングです。retrieve()操作の場合、入力は取得対象のショッピング・カートのIDであり、レスポンスはXML文書です。また、SCAコンポーネントに返されるDataObjectが、BOXMLSerializerを使って作成されます。addItem()操作の場合、入力は、アイテムを追加するショッピング・カートのIDと、アイテム情報を含むDataObjectです。また受信したDataObjectはBOXMLSerializerを使ってバイト配列にシリアライズされ、これがHTTPリクエストとして送信されます。この場合も、返されるレスポンスは単純なストリングです。deleteItem()操作の場合、入力は削除対象のショッピング・カートのIDと削除対象アイテムのIDであり、返されるレスポンスは単純なストリングです。

アダプター・モジュールのテストは、WebSphere Integration Developerのモジュール・デバッグ機能を使って行うことができます。この場合、アダプター・モジュールを駆動するビジネス・モジュールをテストすることができます。アダプター・モジュールとアダプター・コンポーネントは、独自アプリケーションの通常のHTTPサーブレットとして実装されたREST流のサービスと対話動作を行います。そしてREST流のサービスはリクエストに対して意味のある結果を提供します。図16と図17は、retrieve()操作とaddItem()操作に対するリクエストとレスポンスを示しています。これらの図の中の情報は、WebSphere Integration DeveloperのTCP/IPモニターを使ってキャプチャーしたものです。


図16 retrieve()リクエストとレスポンス

Request ---
            
GET /cart/IX4456 HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, 
text/*
User-Agent: Java/1.4.2
Host: localhost:9082
Connection: keep-alive
            
Response ---
            
HTTP/1.1 200 OK
Content-Language: en-US
Transfer-Encoding: chunked
Date: Sun, 01 Oct 2006 20:39:54 GMT
Server: WebSphere Application Server/6.0
            
<?xml version="1.0" encoding="UTF-8"?>
<mystore:ShoppingCart xmlns:mystore="http://mystore.com" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:type="mystore:ShoppingCart">
    <cartID>IX4456</cartID>
    <lineItems>
        <lineItemID>lix45</lineItemID>
        <itemID>Pyu879</itemID>
        <price>9.3</price>
    </lineItems>
    <lineItems>
        <lineItemID>liy146</lineItemID>
        <itemID>R78uyt</itemID>
        <price>4.5</price>
    </lineItems>
</mystore:ShoppingCart>


図17 addItem()リクエストとレスポンス

Request ---
            
PUT /cart/IX4456 HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, 
text/*
User-Agent: Java/1.4.2
Host: localhost:9082
Connection: keep-alive
Content-Length: 275
            
<?xml version="1.0" encoding="UTF-8"?>
<mystore:LineItem xmlns:mystore="http://mystore.com" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:type="mystore:LineItem">
    <lineItemID></lineItemID>
    <itemID>TY456</itemID>
  <price>13.23</price>
</mystore:LineItem>
            
Response ---
            
HTTP/1.1 200 OK
Content-Language: en-US
Transfer-Encoding: chunked
Date: Sun, 01 Oct 2006 20:47:23 GMT
Server: WebSphere Application Server/6.0
            
http://.../cart/li89uy

上に戻る

インバウンド・アダプター

ここで、既存の SCAサービスをREST流の世界に公開することを考えてみてください。ここでは単純で均衡をとるために、SCAインターフェースは図12と図13に示したものと同じだとします。さらに、このインターフェースと型を含むライブラリーが存在するとします。そして最後に、REST流のインターフェースの定義を自由に決められるとします。こうした前提とすると、図10に示した定義、つまりSCAインターフェースを、ほぼ変更せずにマッピングしたものを再利用することができます。

図7の左側に示した形式のアダプターに関する次のステップは、アダプター・モジュールとして動作するビジネス・モジュールを作成することです。アダプター・モジュールは、図18のようにスタンドアロン参照とインポートを含む必要があります。さらに、スタンドアロン参照を呼び出すサーブレットを作成する必要があります。この図は、アダプター・モジュールによって呼び出されるビジネス・モジュールも示しています。この場合、インポートとエクスポートに使用されるバインディングはどんなものでもよく、この例ではデフォルトの(SCA)バインディングを使っています。

アダプター・モジュールのSCA部分を作成するためには、下記を行う必要があります。

  1. ビジネス・モジュールを作成します。依存関係エディタを使ってライブラリーを追加します。
  2. スタンドアロン参照とインポートをモジュールに追加します。インポートに適当な名前を付けます(スタンドアロン参照はリネームできません)。
  3. インポート用のインターフェースをMyShoppingCartに設定します。
  4. スタンドアロン参照をインポートに接続します。WSDLインターフェースをJavaインターフェースに変換するかを尋ねるダイアログ・ボックスの中で、Yesをクリックします。
  5. モジュールを保存します。まだモジュールをデプロイしてはいけません。



図18 インバウンド・アダプター

必要なサーブレットをアダプター・モジュールに追加するためには、動的なwebプロジェクトを作成し、そのプロジェクトの中でサーブレットを作成し、できあがったwebアーカイブ・ファイルを、アダプター・モジュールを表すエンタープライズ・アーカイブ・ファイルに追加します。この場合も、ツールに慣れているという前提で要約すると、下記を行う必要があります。

  1. 動的なwebプロジェクトを作成します。その結果として表示されるダイアログの中でShow Advancedボタンをクリックし、その webアーカイブを新しいエンタープライズ・アーカイブに追加します。注意: できあがるWARファイルを、アダプター・モジュールに対応するEARに置くことができます。ツールがプロジェクトの「クリーニング中」にwebプロジェクトを削除してしまわないように、WARを独自のEARに置くのは得策です。
  2. 新しい動的webプロジェクトをアダプター・モジュールのEARに追加します。そのためには、EARのデプロイメント記述子を開き、Moduleタブで WARを追加します。
  3. ライブラリーを動的webプロジェクト用のJava Build Pathに追加します。そのためには、プロジェクトのプロパティー・エディターを使います。
  4. 新しいサーブレットをプロジェクトに追加します。そのためには、デプロイメント記述子を右クリックしてNew -> Servletを選択するのが最も簡単です。新しいマッピングはサーブレットの作成中に追加することもでき、あるいは動的webプロジェクトのデプロイメント記述子を操作して追加することもできます。この例の場合では、"../cart" と "../cart/*" をマッピングする必要があります。デプロイメント記述子を保存します。
  5. エンタープライズ・アプリケーションのデプロイメント記述子を編集し、WARをアダプター・エンタープライズ・アプリケーションに追加します。デプロイメント記述子を保存します。
  6. 今度はアダプター・モジュールをテスト環境にデプロイします。

下記の図19は、詳細を追加したサーブレットの実装を示しています。init()メソッドは、ビジネス・オブジェクトを作成するためのBOFactoryと、DataOjbectを(デ)シリアライズするためのBOXMLSerializer、そしてMyShoppingCart Standalone Referenceにアクセスするためのサービス・インターフェースを作成します。ユーティリティー・メソッドは、URLからの必要なID取得と、HTTP出力の送信を簡単にします。サーブレットの振る舞いを調整するための、いくつかのストリングがあることに注意してください。こうしたストリングは、サーブレットを再利用しやすくするためのサーブレット初期化パラメーターである場合もあります。


図19 Servletの実装

package com.test;
            
public class XMLHTTPIn extends HttpServlet implements Servlet {
            
    private BOFactory factoryService = null;
    private BOXMLSerializer xmlSerializerService = null;
    private MyShoppingCart cart = null;
            
    private String namespace = "http://mystore.com";
    private String root_pathInfo = "xxx/cart";
    private String serviceName = "ShoppingCart";
            
    protected void doGet(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
        // get cart id
        String[] ids = getIDs(request);
        // retrieve the shopping cart
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        try {
            DataObject reply = cart.retrieve(ids[0]);
            xmlSerializerService.writeDataObject(reply,
                namespace, serviceName, baos);
        } catch (ServiceBusinessException e) {
            e.printStackTrace(System.out);
        }
        sendOutput(response, baos.toByteArray());
    }
            
    protected void doPut(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
            
        // get cart id
        String[] ids = getIDs(request);
            
        String reply = null;
        if (ids == null) { // create request
        
            try {
                reply = cart.create();
            } catch (ServiceBusinessException e) {
                e.printStackTrace(System.out);
            }
            
        } else { // add item request
            // get input (line item)        
            ServletInputStream sis = request.getInputStream();
            BOXMLDocument doc =
                xmlSerializerService.readXMLDocument(sis);
            DataObject parm = doc.getDataObject();
            
            try {
                reply = cart.addItem(ids[0], parm);
            } catch (ServiceBusinessException e) {
                e.printStackTrace(System.out);
            }    
        }
        // add information to reply to produce URL
        reply = namespace+"/"+root_pathInfo+"/"+reply;
        sendOutput(response, reply.getBytes());
    }
            
    protected void doDelete(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
            
            // get cart id
            String[] ids = getIDs(request);
            
            String reply = null;
            if (ids[1] == null) { // delete cart
                try {
                reply = cart.delete(ids[0]);
            } catch (ServiceBusinessException e) {
                e.printStackTrace(System.out);
            }    
            
            } else { // delete item
                try {
                    reply = cart.deleteItem(ids[0], ids[1]);
                } catch (ServiceBusinessException e) {
                e.printStackTrace(System.out);
            }
        }
        sendOutput(response, reply.getBytes());
    }
            
    public void init(ServletConfig arg0)
            throws ServletException {
        super.init();
        factoryService =
            (BOFactory) new ServiceManager().
            locateService("com/ibm/websphere/bo/BOFactory");
        xmlSerializerService = (BOXMLSerializer)
            new ServiceManager().locateService
                ("com/ibm/websphere/bo/BOXMLSerializer");
            
        try {
            cart = (MyShoppingCart) ServiceManager.INSTANCE.
                locateService("MyShoppingCartPartner");
        } catch (Exception ex){
            System.out.println(ex.getMessage());
        }
    }
            
    private String[] getIDs (HttpServletRequest request) {
        System.out.println("in getIDs");
        // get  ids
        String pi = request.getPathInfo();
        if (pi == null) { // no ids at all
            return null;
        } else { // at least a cart id
            String[] ids = new String[2];
            // get cart id
            int last = pi.lastIndexOf('/');
            if (last == 0) { // cart id only
                String cartID = pi.substring(1);
                ids[0] = cartID;
                ids[1] = null;
            } else { // item id also
                String cartID = pi.substring(1, last);
                ids[0] = cartID;
                String lineItemID = pi.substring(last + 1);
                ids[1] = lineItemID;
            }
            return ids;
        }
    }
            
    private void sendOutput(HttpServletResponse response,
            byte[] output) throws IOException {
        response.getOutputStream().write(output);
        response.getOutputStream().close();
    }
}

doGet()メソッドは必ずretrieve()操作を呼び出します。BOXMLSerializerは、返されたDataObjectをバイト配列に変え、レスポンスに入れて返送する際に役立ちます。doPut()メソッドはcreate()メソッドまたはaddItem()メソッドを呼び出しますが、どちらを呼び出すかはURLの内容に依存します。前者の場合、サーブレットは単純に create() 操作を呼び出します。後者の場合、サーブレットは、addItem() 操作を呼び出すためのDataObjectをBOXMLSerializerを使って作成します。どちらの操作の場合にも単純なIDが返されますが、このIDをURLに変換する必要があることに注意してください。doDelete()メソッドはdelete()メソッドまたはdeleteItem()メソッドを呼び出しますが、どちらを呼び出すかはURLの内容に依存します。前者の場合、サーブレットは単純にcart()操作を呼び出します。後者の場合、サーブレットは単純にaddItem()操作を呼び出します。どちらの操作の場合も、単純なストリングが返されます。

アダプター・コンポーネントをテストするために、REST流のリクエストを生成する単純なJavaアプリケーションを使うことができます。ビジネス・モジュールの中にビジネス・コンポーネントを実装すれば、意味のある結果をテストから得ることができます。もう1度、図16と図17をよく見てください。今や、これらの図の中のリクエストはREST流のサービス・リクエスターから来るのであり、レスポンスはSCAプロバイダーから来るのです。


上に戻る

まとめ

この記事では、WebSphere Process Server製品とWebSphere Enterprise Service Bus製品のSCAモジュールを使ってREST流のサービスと対話動作をする際に考慮すべき事項について、いくつか解説しました。さらに、アウトバウンド、インバウンド両方の「REST流アダプター」を実装するための具体例を示しました。この記事で解説した考慮事項や例は、記事の中で説明したREST流の方式には必ずしも従わない、HTTPベースの(リソースに記述された)一般的な対話動作を処理する場合にも利用することができます。

謝辞

この記事で取りあげた実装に関して重要な判断をする上で助言をくださった、WebSphere Enterprise Service Busの開発リーダー、Rob Phippen氏に感謝いたします。


参考文献
Building Web services the REST way (US)
この記事は、REST(Representational State Transfer)の一般的な方式を説明しています。
Connect non-SOAP HTTP requesters and providers to WebSphere Application Server V6 Enterprise Service Bus (US)
この記事は、ベースとなるWebSphere Application Server V6に対して、この記事と似た方法を説明しながら、HTTPパラメーターやクッキーに関して詳細に説明しています。
WebSphere ESB developer resources page (US)
アプリケーションとサービスを統合してSOAをサポートする上で、柔軟な接続インフラとしてWebSphere ESBを使うための技術リソースです。
WebSphere ESB製品ページ (US)
製品に関する説明やニュース、トレーニング情報、サポート情報、その他豊富な情報が用意されています。
WebSphere Enterprise Service Bus(ESB)information center (US)
WebSphere ESBをインストールし、構成し、使用するための概念情報、タスク情報や参照情報を取り揃えた EclipseベースのWebポータルとして、すべての WebSphere ESBドキュメンテーションに1ヶ所からアクセスすることができます。
WebSphere ESB documentation library (US)
WebSphere ESB製品のマニュアル類が用意されています。
WebSphere ESB FAQs (US)
新しい WebSphere ESB製品について、また他のWebSphere製品との関係についての基本的な質問と回答が用意されています。
WebSphere ESB support (US)
サポートに関する問題や、それらに対するソリューション、さらにダウンロードや修正、問題追跡、その他豊富な情報を備えた検索可能なデータベースです。
WebSphere ESB の記事: Developing custom mediations (US)
この記事はカスタム・メディエーション・プリミティブについて、より複雑な使い方を説明しています。
WebSphere Process Server developer resources page (US)
WebSphere Process Serverを使うためのヒントを提供する技術リソースです。
WebSphere Process Server product page
製品に関する説明やニュース、トレーニング情報、サポート情報、その他豊富な情報が用意されています。
WebSphere Process Server information center (US)
WebSphere Process Serverをインストールし、構成し、使用するための概念情報、タスク情報や参照情報を取り揃えた EclipseベースのWebポータルとして、すべてのWebSphere Process Serverドキュメンテーションに1ヶ所からアクセスすることができます。
WebSphere Process Server requirements (US)
WebSphere Process Serverに関する、ハードウェアとソフトウェアの要件です。
WebSphere Process Server support (US)
サポートに関する問題や、それらに対するソリューション、さらにダウンロードや修正、問題追跡、その他豊富な情報を備えた検索可能なデータベースです。
developerWorks WebSphere Business Integrationゾーン (US)
WebSphere Business Integrationに関するハウツー記事やダウンロード、チュートリアル、教育資料、製品情報などが豊富に用意されています。
WebSphere Business Integration 製品ページ
ビジネス・ユーザーも技術ユーザーも、すべてのWebSphere Business Integration製品の概要を手軽に知ることができます。
WebSphere forums (US)
製品に特化したフォーラムとして、技術的な質問に対する回答を得ることができ、皆さんの専門的技術を他のWebSphereユーザーと共有することができます。
Most popular WebSphere trial downloads (US)
鍵となるWebSphere製品の無料の試用ダウンロードです。
Trial downloads for IBM software products (US)
えり抜きの製品群、IBM® DB2®やLotus®、Rational®、Tivoli®、WebSphere®などの無料の試用ダウンロードです。
developerWorks technical events and Webcasts (US)
IBMのエキスパートによる無料の技術セッションとして、皆さんの学習期間を短縮し、また困難なソフトウェア・プロジェクトの品質や結果を改善する上で役立つはずです。セッションの中には、1時間のウェブキャストもあれば、世界中の各都市で開催される、半日あるいは1日のライブ・セッションもあります。
developerWorks blogs (US)
ソフトウェア・エキスパートによる、継続的で自由形式のコラムであり、皆さんのコメントを書き込むことができます。Grady Boochによる、ソフトウェア・アーキテクチャーに関するブログを見てください。

上に戻る

著者について

Greg Flurryは、IBM Software GroupのEnterprise Integration SolutionsのSenior Technical Staff Memberです。彼は頻繁に顧客の元に出張し、顧客がサービス指向のソリューションを定義し、実装するためのサポートを行っています。彼の業務の中心は、比較的新しいWebSphere Version 6 Platform Messaging技術や、こうした技術を顧客ソリューションに適用することです。彼は多作の著者として、これまで50以上の記事を執筆してきました。Gregと彼の執筆した記事について読むために、WebSphere author spotlight page (US)をご覧ください。


上に戻る

レベルマークについて

このページで紹介されている情報はレベル別にカテゴライズされています。

上級者向け
中級者向け
初級者向け
入門者向け

製品情報
WebSphere Process Server  
WebSphere Enterprise Service Bus