POJOベースのドメインアプリケーションをWebサービスとして公開するはじめにエンタープライズアプリケーション開発において結合の緩いサービスが受け入れられるようになったこと、さらにSOA(サービス志向アーキテクチャ)をめぐる以前からの動向を受けて、企業の間では、リモートからの起動と実行が可能なコンシューマブルサービスについての関心が高まっています。またサービスドリブン型アーキテクチャを実現する媒体としてSOAPベースのWebサービスがもつ価値と重要性についても、この数年間で広く認識されるようになりました。 Webサービスの構築法はさまざまですが、特に企業の場合は、ビジネス機能を実装している既存アプリケーションに既にかなりの投資をしているのが普通です。そのためこうしたケースでは、既存のドメイン機能を安全で簡単かつ効率的な方式でWebサービスとして提供できるデザインが求められています。 本稿ではAxis2を用いることで、十分なスケーラビリティを維持しつつ、既存のPOJOドリブンJ2EEアプリケーションをWebサービスとして公開する方法について説明します。これらのサービス作成と制御には、現在多くのJ2EEアプリケーションにおいてDI(Dependency Injection:依存性注入)コンテナとして利用されているSpringを利用します。また最後にAxis2エンタープライズWebサービスの配備戦略を解説し、IBM WebSphereなどのアプリケーションサーバに固有の問題についても触れることにします。 アプリケーション通常のJ2EEアプリケーションは階層構造を取っており、プレゼンテーション層とビジネス/ドメイン層とが分離しているのが普通です。すべてのコンポーネントを統合するのは、Springなどのフレームワークまたはカスタムファクトリ/コントローラの役割です。本稿では、簡単なWeb層とドメイン層を用いてビジネスロジックをカプセル化するアプリケーションを考えることにします。 今回のサンプルアプリケーションでは、単純化のためWeb層とドメイン層を共通のプロジェクト構造に統合していますが、本来これらは明確に区別されるものです。図1に、サンプルアプリケーションAccountWebの構成を示します。 図1 既存アプリケーションの構成 ![]() このアプリケーションは、フロントエンドで使われるAccountManagerというドメイン機能を持ちます。AccountManagerはAccountManagerImplとして実装され、ドメインオブジェクト ドメインロジックインターフェイスを見ると分かるようにAccountManagerには下記の2つのメソッドが定義されています。 public interface AccountManager { public Account getAccount(String accountId); public void setStatus(String accountId, Boolean status); } 本稿の残りの部分では、Axis2およびSpringを用いてAccountManager機能とその2つのメソッドを公開し、コンシューマブルなWebサービスとして外部から利用させる方法について説明します。 Axis2を用いたWebサービスの構築Webサービスを構成するコードのうち、プロバイダサイドのコード部分は「スケルトン」と呼ばれ、コンシューマサイドのコード部分は「スタブ」と呼ばれます。スタブとスケルトンは、マーシャリングおよびアンマーシャリングのリクエストで使用されます。よって、まず目的とするサービスのプロデューササイドのコンポーネントを作成する必要があります。 Webサービスの構築法には、ボトムアップ形式とトップダウン形式のアプローチがあります。ボトムアップのアプローチでは、最初にJavaクラスを構築しておき、そこからサービス記述子やWSDLを生成します。トップダウン形式のアプローチでは、WSDLを構築しておき、そこからJavaクラスを生成します。今回のサンプルアプリケーションでは、ビジネス機能が既にJavaクラスとして実装されているので、ボトムアップのアプローチを採用します。 Axis2は同期および非同期サービスをサポートしていますが、今回のサンプルでは同期サービスのみを使用します。Axis2とSpringを用いて既存のドメインクラスからWebサービスを生成するステップを簡単にまとめると次のようになります。
サービスの生成と設定が終わったら、次の作業を行います。
ここから先の手順を実行するには、Axis2バイナリをダウンロードし、アプリケーションクラスパスのライブラリディレクトリにインストールしておく必要があります。WebSphereやWebLogicなど一部のアプリケーションサーバでは、サーバにデフォルトで使われるライブラリとこれらのライブラリが競合する場合があります。この問題については、配備の手順を解説する際に説明します。現段階では、こうした競合はクラスローダおよびその他のクラスパスポリシーを操作することで回避できることを知っておけば十分でしょう。本稿のダウンロードファイルには、すべてのサンプルコードが収録されています。 WSDLの生成既存クラスからWSDLを生成するには、Axis2ディストリビューションに用意されているwsdl2javaなどのコマンドライン実行可能バイナリを使用する方法があります。個々のバイナリをスタンドアロンバイナリおよびANTタスクとして使用する方法は、Axis2の付属ドキュメントに詳しく解説されています。Axis2プロジェクトからは、Eclipse、IntelliJ IDEA、Maven 2用のプラグインも提供されています。 今回のサンプルではEclipseプラグインを使用します。このプラグインは、機能的にはコマンドライン形式のバイナリより劣りますが、Webサービスの生成プロセスを大幅に効率化できるという長所があります。またこのプラグインを用いると、WSDL-to-JavaおよびJava-to-WSDLの両方向の生成をサポートできます。 柔軟性と分かりやすさのために、生成されたWSDLなどのWebサービス関連のコードを他のプロジェクト内に配置するとよいでしょう。そこで、コンポーネントを生成する前に、IDE内で空のソースとバイナリツリーを含む空のJavaプロジェクトを作成してください。このプロジェクトの名前はAccountWSにします。 これでEclipseプラグインが使用可能になり、 これで、新規の空プロジェクトAccountWS内に「services.wsdl」という名前のファイルが作成されるはずです。このWSDLは、次のように入力/出力の値に応じた2つのサービスを定義しています。 <xs:element name="getAccount"> <xs:complexType> <xs:sequence> <xs:element name="accountId" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="getAccountResponse"> <xs:complexType> <xs:sequence> <xs:element name="return" nillable="true" type="ns0:Account" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="setStatus"> <xs:complexType> <xs:sequence> <xs:element name="accountId" nillable="true" type="xs:string" /> <xs:element name="status" nillable="true" type="xs:boolean" /> </xs:sequence> </xs:complexType> </xs:element> WSDLが生成されたら、 クライアントおよびサーバサイドコードの生成(スタブおよびスケルトン)WSDLの生成が完了したら、次のステップとして、プロデューサおよびコンシューマアプリケーション用のスケルトンおよびスタブをそれぞれ生成します。ここでもEclipseプラグインを使用し、データバインディング用のAxis Data Binding(ADB)オプションを用いてサーバサイドクラスの生成を行います。Axis2およびEclipseプラグインはXMLBeansなど他のデータバインディングもサポートしていますが、今回のサンプルでは単純性からADBを使用します。 ただしADBには各種の制限が付随します。例えばエンタープライズアプリケーションの構築に使用するには、機能面で力不足と思われます。操作面での柔軟性やスキーマバインディング用の機能を完全に備えたツールが必要な場合は、JiBXなどのXMLバインディングツールの方が適しているかもしれません(Axis2はJiBXをサポートしています)。現状でAxis2プロジェクトからはエンドツーエンドのコード生成ツールは提供されていませんが、JiBXプロジェクトからはそうした処理を比較的簡単にこなせるツールが提供されています。 以下、図3の一連のスクリーンショットは、プロデューササイドのスケルトンおよびクライアントサイドのスタブの生成ステップをまとめたものです。 今回のサンプルでは、スケルトンおよびスタブのディレクトリ構成はそれぞれ図4および図5のようになります。 図4 生成されるサーバサイド(スケルトン)コード ![]() 図5 生成されるクライアントサイド(スタブ)コード ![]() Springとの統合 各種のJ2EEアプリケーションは、Spring IoCコンテナを使用することで、クラスの起動、依存性管理、インターセプションに関する高度な柔軟性を確保しています。これらの機能は既にアプリケーションの一部になっているので、これをさらに拡張してAxis2ドリブンのWebサービスに取り込みたいところです。これを簡単に行うために、Axis2には、 SpringとAxis2を併用するときの基本的な考え方は、Springを利用して、アプリケーションBeanをサービスのAxis2 Message Receiverに注入するというものです。Axis2はSpringアプリケーションのコンテキストサポートを認識し、適切なサービス実装Beanを特定します。そしてメッセージレシーバは、このBeanを用いた実装の委託を行います。こうした処理に関するすべての設定は、サービス配備記述子である「services.xml」の内部で行われます。 それでは、サンプルの実装クラス用にSpringの「applicationContext.xml」を定義し、Spring対応のサービスクラスを作成してみましょう。Springコンテキストファイルは下記のように変更します。 <beans> <!-- Axis2 web service, but to Spring, its just another bean that has dependencies --> <bean id="springAccountservice" class="com.corp.account.domain.accountmanager.AccountserviceSkeleton"> <property name="service" ref="accountManager"/> </bean> <!-- just another bean/interface with a wired implementation, which is injected by Spring into the web service --> <bean id="accountManager" class="com.corp.account.domain.impl.AccountManagerImpl"> </bean> </beans> 「accountManager」はPOJO実装クラスを示しており、「springAccountservice」はプロデューササイドのスケルトンをSpringのロードBeanとして定義しています。ここでの目的はこの「springAccountservice」を取得して、サーバメッセージレシーバに認識させることです。そのためには「services.xml」を下記のように編集します。 <parameter name="serviceObjectSupplier" locked="false"> org.apache.axis2.extensions.spring.receivers. ここで定義している「serviceObjectSupplier」というパラメータには、Axis2によりオブジェクトサプライヤクラスが入力として与えられます。 <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> 最後に施す変更は、Skeleton実装クラスに関するものです。ここでは実装クラスの注入を行うので、事前ロードされたBeanによるアップデートを行うために、Spring用に空のセッター(setter)メソッドを作成する必要があります。図6は、SpringとAxis2の併用に関する基本的な概念と手順をまとめたものです。 図6 SpringとAxis2の併用 ![]() Springを用いてAxis2 Webサービスを実装するという手法には、いくつかのメリットがあります。POJOベースのビジネス機能実装を拡張してWebサービス呼び出しをサポートすることが簡単にできますし、そうしたアプリケーションに備わった依存性の注入、ワイヤリング、インターセプタを継続して使用することができます。 Webサービスと既存アプリケーションドメインとの統合 ここまでの手順でWebサービスのプロバイダサイドの構造は用意できたので、次は既存のPOJOドリブンドメイン機能との統合を行います。先に見たように、Springによる public void setservice(AccountManager service) { this.service = service; } このオブジェクトを用いると、ドメインクラス中のメソッドを起動することができます。このドメイン関数はドメインオブジェクトを返すので、これをWebサービスのスキーマオブジェクトにマッピングします。このプロセスをさらに最適化するには、JiBXなどのツールを使ってドメインオブジェクトに直接マッピングします。変更後のサーバサイドコードは次のようになります。
Account acct = service.getAccount(accountId);
GetAccountResponse response = new GetAccountResponse();
com.corp.account.domain.bo.xsd.Account acctXSD
= new com.corp.account.domain.bo.xsd.Account();
acctXSD.setAccountId(accountId);
acctXSD.setAddress(acct.getAddress());
acctXSD.setFirstName(acct.getFirstName());
acctXSD.setLastName(acct.getLastName());
acctXSD.setStatus(acct.getStatus());
response.set_return(acctXSD);
このコードの最後のステートメントは、サービスレスポンスとしての戻り値を示しています。 サービスの配備Axis2の特徴の1つはその配備モデルにあります。Axis2の配備モデルはJ2EE配備のアーカイブベースの構造を踏襲しており、配備およびサービス記述子を包括するアーカイブを使用します。このアーカイブはAxis Application Archive(AAR)と呼ばれています。 配備するサービスのアーカイブを作成するには、Axis2プロジェクトから提供されている別のEclipseプラグインを使用します。その際に、アーカイブ作成に関するオプションを指定する必要があります。この場合もコマンドラインオプションを使用できます。このアーカイブファイルには、すべてのサービスコードに加えて、サービス記述子として機能する「service.xml」も取り込まれます。基本的な考え方は、このアーカイブをAxis2 Webアプリケーションを通じてアップロードすることで、アプリケーションのホット配備とアップデートを実行時に行うというものです。 この他に、これらのサービスを既存EARの一部として配備する場合(組み込み型のAxis2配備)のオプションとして、WebプロジェクトにおけるWEB-INFの下層に特殊な「services」ディレクトリを作成し、このディレクトリにアーカイブをドロップするという方法もあります。エンタープライズアプリケーションの多くは管理された環境下で事前に組まれたリリース計画に従って配備されるので、通常は後者のオプションの方が好まれます。後者の場合、アプリケーションの配備EARの中にWebサービスを取り込むことができるからです。この機能を利用すると、管理、所有権、説明責任の取り扱いが簡単化されます。 なお、このWebプロジェクトのWARは(アプリケーションEAR内の)独立したプロジェクトにすることをお勧めします。他のJSP/Servlet WebアプリケーションのWARと共有するべきではありません。このようにしておくと柔軟性が向上し、またコンテナのWebアプリケーションライブラリとの競合を防止できます。 展開形式での配備アプローチただし、Springサポートを利用してAARファイルをWebSphereやWebLogicなどのエンタープライズアプリケーションサーバに配備する際には、いくつかの問題があります。よってこれらのサーバについては、展開形式での配備アプローチが推奨されます。下記の手順は、Axis2アプリケーションをWebSphere Application Serverプラットフォームに展開形式で配備するステップをまとめたものです。
<servlet> <servlet-name>AxisServlet</servlet-name> <servlet-class> org.apache.axis2.transport.http.AxisServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>AxisRESTServlet</servlet-name> <servlet-class> org.apache.axis2.transport.http.AxisRESTServlet </servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>AxisServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>AxisRESTServlet</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> 動作確認アプリケーションの配備と起動が正常に行われたら、次のアドレスにアクセスして動作を確認します。
RESTバージョンの場合は、次のアドレスで確認します。
WebLogicの場合も、基本的に同様のプロセスを用います。 サービスのテストサービスの最終的なテストを行うには、コンシューマ側から検証します。REST実装については、ブラウザから直接実行することで簡単に検証できます。図7に、REST起動時の入力および出力を示します。 アクセス先のURL:
図7 RESTベースのサービスの起動とレスポンス ![]() ここまで来れば、後は簡単なクライアントを作成することで、SOAP/HTTPを介したサービスの起動テストが行えます。ここでは単純なJavaプロジェクトを作成して、先に生成されたスタブを収めたクライアントjarを取り込みます。実際のアプリケーションでは通常、サービスプロバイダがこれらのファイル(またはコンシューマがスタブやメッセージレシーバの生成に使用するWSDL)を提供します。ここでは説明上の便宜的な措置として、ADBを使用してサービスを起動することにします。複雑なアプリケーションを扱う場合は、JiBXを利用して、Axis2に統合されているスキーマバインディングを行います。Axis2に付属するwsdl2javaというコマンドライン形式のユーティリティは、JiBXクライアントの生成をサポートしています。 SOAPサービスのテスト用クライアントとして使用するコードを次に示します。 AccountserviceStub stub = new AccountserviceStub( "http://localhost:9080/AccountWeb/services/Accountservice"); .. AccountserviceStub.GetAccount req = new AccountserviceStub.GetAccount(); req.setAccountId(accountId); AccountserviceStub.GetAccountResponse res = stub.getAccount(req); name = res.get_return().getFirstName() + " " + res.get_return().getLastName(); このクラスを実行してクライアントとサーバの間で交わされるTCP/IPトラフィックを監視すると、呼び出しが正常に行われた場合に、次のような情報が交換されることが確認できるはずです。これらの呼び出しは リクエスト
<?xml version="1.0" encoding="http://www.w3.org/2003/05/soap-envelope"?> <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"> <soapenv:Header/> <soapenv:Body> <ns2:getAccount xmlns:ns2="http://AccountManager.domain.account.corp.com/types"> <ns2:accountId>2</ns2:accountId> </ns2:getAccount> </soapenv:Body> </soapenv:Envelope> レスポンス
<?xml version="1.0" encoding="http://www.w3.org/2003/05/soap-envelope"?> <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"> <soapenv:Header/> <soapenv:Body> <ns2:getAccountResponse xmlns:ns2="http://AccountManager.domain.account.corp.com/types"> <ns2:return> <ns6:accountId xmlns:ns6="http://bo.domain.account.corp.com/xsd"> 2 </ns6:accountId> <ns7:address xmlns:ns7="http://bo.domain.account.corp.com/xsd"> 2222, Lincoln Av, New York </ns7:address> <ns8:firstName xmlns:ns8="http://bo.domain.account.corp.com/xsd"> Jane </ns8:firstName> <ns9:lastName xmlns:ns9="http://bo.domain.account.corp.com/xsd"> Smith </ns9:lastName> <ns10:status xmlns:ns10="http://bo.domain.account.corp.com/xsd"> false </ns10:status> </ns2:return> </ns2:getAccountResponse> </soapenv:Body> </soapenv:Envelope> 将来的な展望本稿では、既存ドメインに基づいてWebサービスを作成する基本的な方法を紹介しました。Webサービスを用いたより実用的なエンタープライズアプリケーションを構築する場合は、セキュリティやメッセージングの信頼性など、より複雑な問題に対処する必要があります。Axis2のアーキテクチャを利用すると、これらの機能を比較的簡単に、モジュール化された方法で実装できるというメリットがあります。このプロセスは大雑把に言えば、サービス記述子を編集して必要なモジュールへの参照を追加し、それぞれのポリシーを設定するという流れになります。 本稿では、J2EEアプリケーション内の既存のPOJOベースのドメイン機能を公開するための簡単な代替手段としてAxis2を紹介しました。このような形でAxis2を利用すると、これまでアプリケーションアーキテクチャに行ってきた投資を無駄にせず、コードの書き直しを最小限に抑えた上で、既存のPOJOベースのドメインアプリケーションをリモートから利用できる堅牢なWebサービスとして公開することができます。 著者紹介Ramanujam A. Rao(Ramanujam A. Rao)
ソフトウェア設計者兼エンジニアで、エンタープライズアプリケーションの設計と配備を専門とする。現在は、エンタープライズアプリケーションアーキテクチャの分野におけるコンサルティングおよび、J2EEプラットフォームを用いたスケーラブルな分散型アプリケーションの構築に関するサポートを行っている。連絡先はarrao@acm.org。
|