あなたは、SOAP Webサービスの開発を始めていますか?もしそうなら、あなたは2つの開発スタイルを選ぶことができます。第1に、「WSDLから開始する」もしくは「コントラクトファースト」と呼ばれるもので、WSDLのサービス定義とデータ変換を直接行うための関連するXMLスキーマを設計するものです。第2に、「コードから開始する」もしくは「コードファースト」と呼ばれるもので、サンプルとなるサービスのコードをフレームワークに挿入し、そのコードからWSDLやスキーマを生成するものです。
いずれの開発スタイルでも、最終目標は同じです。つまり、サービスに対しきちんと定義されたWSDLとスキーマが必要なのです。SOAの環境で作業をしている場合、この目標は非常に重要です。SOAはサービスの疎結合を必要とするので、そのインターフェースが固まり、実装から切り離されたものでなければなりません。WSDLとスキーマによって、プラットフォームに依存しないWebサービスによって使用されるXMLメッセージ変換の部分が明確になります。故に、XMLWebサービスによってSOA実装のための基礎となる部分のほとんどが作られます。もし、WSDLとスキーマがしっかりしていなければ、サービスは、直接、または、間接的にサービスプロバイダの管理下にあるクライアントが使用するだけとなるでしょう。しかし、それはSOAではありません。
「コードから開始する」際の課題
Webサービスの開発をコードから開始するという考え方は、WebサービスやSOAの分野の権威ある人たちの多くから難色を示されています。彼らは、コードから開始することで、XMLメッセージの構造がWSDLやスキーマを利用するという目的を超え、個別の実装に結びついてしまうと感じているからです。それは、SOAPエンコーディングのスキーマがRPC/エンコード方式をサポートするものとして広く使われていた頃の、所謂、コードから開始するという考えの原型においては確かに正しいことです。SOAPエンコーディングにより、サービスプロバイダアプリケーションのデータ構造からXMLスキーマが直接生成され、複製されたデータ構造でクライアントコードを実装します。このXMLからデータモデルへ(またはその逆)の自動変換は、初期のSOAPにおいてRPC/エンコード方式を広めた機能です。しかし、それがスタイルを変更しなければならない大きな要因の一つでもあるのです。つまり、それによってサービスのデータ構造が変わる度にスキーマも変更し、クライアントでは新しいスキーマを使用しているコードを再生成しなければならないのです。
図1. 「コードから開始する」ためのSOAPエンコーディングアプローチ
SOAPエンコーディングにより作られた密結合に加えて、XMLデータ表現とスキーマ定義に関する欠点もあります。SOAPエンコーディングは、プログラミング言語から独立した形で定義されたオブジェクトグラフによるXMLシリアライゼーションアルゴリズムです。シリアライゼーションアルゴリズムであるため、XML構造の解析に関する柔軟性がありません。そのアルゴリズムをデータ構造に適用すると、そこから作られるものはそれらの構造に対する SOAPエンコーディングなのです。残念なことに、そのシリアライゼーションのルールは、ドキュメントの妥当性確認を含むrpc/エンコード方式のメッセージ変換以外のどんな目的にもほとんど使えないXMLスキーマという結果になりました。そのフォーマットはパフォーマンスにおいても、比較的貧弱なものです。原因として、構造参照のオーバーヘッド、過度なランタイムタイピング、全コンポーネントに対する子エレメントの利用(属性よりは適切なのですが)が挙げられます。
これらの問題のほとんどは、XMLのやりとりでデータ構造をシリアライズする技術として利用することができるでしょう。しかし、コードから開始する場合、直接シリアライズ化することによってデータモデルを公開する必要性はありません。現在のWebサービスでは、ほとんどの場合、いくつかのデータバインディングの形式を利用したデータモデルとXML間の柔軟な変換をサポートしています。データバインディングによって、データのXML表現を超えた制御が可能です。つまり、スキーマ定義を実際のデータモデルから少しだけ分離させることができ、データに適したXML表現を選ぶことができるということです。データバインディングのアプローチによって、SOAPエンコーディングに関する多くの問題は、もはや関係のないものとなります。
「WSDLから開始する」際の課題
WSDLから開始する際の大きな課題は、コードでの作業と比較したときの、WSDLとスキーマ定義での作業の扱いにくさです。現代のIDEが備えている"知的な"エディタと強力なリファクタリングツールによって、コードの変更は容易になりました。WSDLとスキーマにおける同等のツールにおいては、利用できるものが全くありません。ローカル定義からグローバル定義に変換するといった、ごく基本的なスキーマのリファクタリングでさえも、主要なWSDL やスキーマツールでサポートされていません。
ツールが弱点であるので、WSDLから開始する際には、正しい結果を得るためのWSDLとスキーマに関するしっかりとした理解も必要となります。標準となるものが無い状態で、利用可能なツールが開発者によって使用されると、結果として生じるWSDLとスキーマが、サービスやデータの構造を明らかにするためというよりも不明瞭にするために利用されるという、厄介な混乱が発生することがしばしばあります。WSDLの部分に関しては、効果的な理解を得ることはさほど難しくはありません。しかし、スキーマに関しては難しい話です。W3C XML スキーマ("スキーマ"のフルネームです)の提言では、一般的なプログラミング言語と同等の複雑さで、たくさん努力することで達人になれることを求めています。専任のアーキテクチャチームがある大きな組織では、スキーマのエキスパートを雇ったり養成したりすることができますが、小さな組織においてはスキーマの複雑さがWSDLから始めるためのサービス仕様書のための本質的な障害となります。
WSDLとスキーマ定義の初期セットを開発したとしても、使いやすさの問題が残っています。サービスの複雑なセットによる開発によって、仕様書、プロトタイプ、テストの繰り返しを伴う反復プロセスを常に行わなければならなくなるでしょう。機能が貧弱なツールによる作業の不便さによって、各開発フェーズにおける開発サイクルが妨害されることになります。
コードから開始する際の作業
サービス仕様のためのコードから開始するアプローチに関して現在利用できるツールは、不評を招いているSOAPエンコーディングモデルよりも非常に優れたものです。そのツールは、柔軟性や拡張性があり複雑なデータ構造に対しても有効です。最も重要なのは、コードで定義されたデータ構造とXML表現に対応するものとの間に分離したレイヤーを追加することができることです。
Microsoftの.NET frameworkとSunのJAX-WS 2.0/JAXB 2.0の2つは、一般的な例と言えます。共に、XMLのやり取りの変換を制御するためにソースコードの中に組み込まれた設定情報を利用します(.NETの場合はアトリビュートとして、JAX-WS/JAXBの場合はアノテーションとして)。提供されている組み込みの設定情報には制限があるので、一般的には細かい部分でデフォルトのシリアライズ化の選択とは異なります。それは、必ずしもデータ構造の詳細から分離されるわけではないということを意味しています。例をあげると、もしオブジェクトに新たなフィールドを追加したとすると、フィールドに明確に記載されていない限り自動的にXML表現の一部となります。しかし、それは純粋なシリアライゼーションアプローチと比べると非常によいものと言えます。
図2. コードから開始するための.NETとJAX-WS 2.0/JAXB 2.0アプローチ
JiBXで分離する著者が作ったJiBX (http://www.jibx.org)は、 Javaのデータバインディングフレームワークで(Apache Axis2, XFire, and JiBX/WSのWebサービスで利用可能です)、アプリケーションデータモデルからXML表現の分離をより一層促進するものです。JiBXは、ソースコードから分離したバインディング定義を利用し、それぞれの項目をバインディングの中に明確に記されたXML表現に含める必要があります。データモデルと XML表現の間の構造的な違いとしてバインディングを扱うことができるので、XML表現でデータモデルの長期的な変更を保持することができます。JiBX は、複数のバインディングを同一のコードに適用することもできるので、多くの種類のスキーマタイプのバージョン変更に対しても、単一データモデルで対処することができます。
関連するツールであるJibx2Wsdl (http://www.sosnoski.com/jibx-wiki/space/axis2-jibx/jibx2wsdl) は、コードから開始するアプローチの利用に関して将来的にメリットとなることを示しています。そのツールは、WSDLとスキーマと一緒に該当するJiBX のバインディング定義を生成し、それらが同期が取れていることを保証します。さらに、Javaのソースコードから生成されたWSDLとスキーマに対する JavaDocドキュメントをエクスポートするので、マニュアルの編集をすることなくサービスの記述に関するドキュメントが完成します。 Jibx2Wsdlはデータモデルクラスを利用したバインディングを作成するのに適したアルゴリズムをデフォルトで使用しますが、XMLドキュメント形式でカスタマイズしたものを利用することで自由にアルゴリズムを変更することができます。このカスタマイズは、.NETのアトリビュートやJAX- WS/JAXBのアノテーションでも同様でソースコードの中に組み込む必要はありません。
図3. コードから開始するためのJiBX/Jibx2Wsdlのアプローチ
実際には、Jibx2Wsdlは2段階に分けてコードから開始するアプローチをおこないます。まず初めに生成段階では、Jibx2Wsdlを使用して実際のWSDLとschema定義とそれに対応するバインディング定義を作成します。次にデプロイ段階では、JiBXを使用して生成されたバインディング定義をJavaのクラスに適用します。
開発の初期段階では、簡単に作成してサービスのプロトタイプを洗練させるためにこれら2つのステップを一緒に行うことが可能です。しっかりとサービス定義が決まったら、生成段階は不要になります。つまり、JiBXのバインディング定義は安定した生産物として扱い、バインドしているデータモデルに変更 (フィールドの間違い、クラス構造の変更)が無い間はデプロイメントに直接利用します。変更が発生した場合は、JiBXのバインディングコンパイラがエラーを出力し、デプロイメント段階が失敗します。その時点では、バインディングに合う形にデータモデルを置き換えるか、修正されたデータモデルに合った形にバインディングを修正するかのどちらかとなります。(スキーマによって定義されたXMLフォーマットがある間です。しかし、この部分に関して現在は強要していません)
package com.sosnoski.infoq.ex1;
/**
* Interface for placing orders and checking status.
*/
public interface StoreService
{
/**
* Submit a new order.
*
* @param order
* @return id
*/
public String placeOrder(Order order);
/**
* Retrieve order information.
*
* @param id order identifier
* @return order information
*/
public Order retrieveOrder(String id);
/**
* Cancel order. This can only be used for orders which have not been shipped.
*
* @param id order identifier
* @returntrue
if order cancelled,false
if already shipped
*/
public boolean cancelOrder(String id);
}
/**
* Order information.
*/
public class Order
{
/** Unique identifier for this order. This is added to the order information by the service. */
private String orderId;
/** Customer identifier code. */
private String customerId;
/** Customer name. */
private String customerName;
/** Billing address information. */
private Address billTo;
/** Shipping address information. If missing, the billing address is also used as the shipping address. */
private Address shipTo;
/** Line items in order. */
private List items;
/** Date order was placed with server. This is added to the order information by the service. */
private Date orderDate;
/** Date order was shipped. This is added to the order information by the service. */
private Date shipDate;
...
}
リスト1. サービスとデータモデルのサンプルコード(抜粋)
リスト1は、サービスインターフェースとデータモデルクラスのルート部分の簡単なサンプルです。リスト2は、リスト1のソースコードで表されたもの以外の補足情報を付加するためのJibx2Wsdl設定ファイルです。このケースでは、追加情報としてWSDLとスキーマで使用する名前空間が含まれています。値のあるものにはそれぞれのデータクラスを指定し、子エレメントではなく属性を使用していることを表す表記(名称に@をつけます)を使用します。また、指定したタイプの項目をコレクションに含めることができます。
<custom force-classes="true" namespace="http://ws.sosnoski.com/order/data"
namespace-style="fixed">
<wsdl namespace="http://ws.sosnoski.com/order/wsdl"
wsdl-namespace="http://ws.sosnoski.com/order/wsdl"/>
<package name="com.sosnoski.infoq.ex1">
<class name="Order" requireds="@customerId customerName billTo items"
optionals="orderId orderDate shipDate">
<collection-field field="items" item-type="com.sosnoski.infoq.ex1.Item"/>
</class>
<class name="Address" requireds="street1 city @state @zip"/>
<class name="Item" requireds="@id @quantity @price"/>
</package>
</custom>
リスト2. Jibx2Wsdlのカスタマイズ
<wsdl:definitions ... targetNamespace="http://ws.sosnoski.com/order/wsdl/StoreService">
<wsdl:types>
<xsd:schema ... targetNamespace="http://ws.sosnoski.com/order/wsdl/StoreService">
<xsd:import namespace="http://ws.sosnoski.com/order/data"
schemaLocation="data.xsd"/>
<xsd:element name="placeOrder">
<xsd:complexType>
<xsd:sequence>
<xsd:element type="ns1:order" name="order" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="placeOrderResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element type="xsd:string" name="string" minOccurs="0">
<xsd:annotation>
<xsd:documentation>assigned order identifier</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
...
</xsd:schema>
</wsdl:types>
...
<wsdl:portType name="StoreServicePortType">
<wsdl:documentation>Interface for placing orders and checking status.</wsdl:documentation>
<wsdl:operation name="placeOrder">
<wsdl:documentation>Submit a new order.</wsdl:documentation>
<wsdl:input message="tns:placeOrderMessage"/>
<wsdl:output message="tns:placeOrderResponseMessage"/>
</wsdl:operation>
...
</wsdl:portType>
</wsdl:definitions>
<xsd:schema ... targetNamespace="http://ws.sosnoski.com/order/data">
<xsd:complexType name="order">
<xsd:annotation>
<xsd:documentation>Order information.</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element type="xsd:string" name="orderId" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Unique identifier for this order. This is added to the order information by the service.</xsd:documentation>
</xsd:annotation>
</xsd:element>
...
<xsd:element ref="tns:address" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Shipping address information. If missing, the billing address is also used as the shipping address.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence/>
<xsd:attribute type="xsd:string" use="required" name="id"/>
<xsd:attribute type="xsd:int" use="required" name="quantity"/>
<xsd:attribute type="xsd:float" use="required" name="price"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
...
</xsd:complexType>
</xsd:schema>
リスト3. WSDL とその schema (抜粋)
一度サービスを構成するための基本的なサンプルコードを見れば、より詳細な部分を埋め、異なるステークホルダーのニーズにフィットしたデータモデルに変換することは非常に簡単です。生成されたバインディングは、Apache Axis2のWebサービスフレームワーク(あるいはXFireか、リリース予定のJiBX/WS)を使用したサービスをデプロイするためのWSDLとスキーマに結合して利用することができます。そして、同じデータモデルをJavaクライアントに直接利用することができます。当然、他のフレームワークや他のクライアントを利用することもできます。何故なら、生成されたWSDLとスキーマはサービスのインターフェースを定義しているからです。しかし、開発の初期段階では、単一バージョンのコードで作業ができることが本質的な利便性となり、Jibx2Wsdl によってそれを行うための非常に容易な方法を与えてくれます。
結論
SOAコミュニティが受け入れているWSDLから開始するという考え方は、常に正しいアプローチと言えます。しかし、実社会の選択肢は、一つの判断によって示されるものよりもより複雑なものです。WSDLとスキーマを学ぶ観点と、これらのフォーマットをサポートしているものの扱いにくいツールを使用して頻繁に作業するという両面において、WSDLから開始するには多くの投資を必要とします。先行投資を多くしなければならない上に、きれいにきちんと構造化されているのは言うまでも無く、結果が思ったとおりに得られる保証もありません。コードから開始するアプローチにも、無意識のうちにサービスの記述を特定の実装に結び付けてしまう可能性を含んでおり、それ自身に潜在的な破たん要因を持っています。しかし、現在のデータバインディングフレームワークでは、データモデルの詳細を実際のXML表現やから分離することができます。現実的な観点からすると、開発者はWSDLやスキーマよりもコードで、常により生産性のある作業をします。多くの場合、Webサービスの開発においては、古い技術を利用して実装されたサービスの形式といった、何かしら既に存在するコードを元に始めるのが現実です。したがって、たとえエキスパートからどんな意見を言われようとも、コードから開始するアプローチはこれからもWebサービスの開発において重要な位置を占めるでしょう。
データバインディングのタイプやWebサービスのフレームワークの利用に関わらず、迅速にサービスを作るためにコードから開始するアプローチを利用することは可能です。サービスの機能がきちんと決まりユースケースに対する検証が完了したときから、いつでもその関係を壊すことができるのです。言い換えれば、新たな開始点として生成されたWSDLとスキーマ定義を取得し、組織のニーズに適合しないXMLのどの部分であっても、必要に応じてクリアして修正すればよいのです。そして、選択したフレームワークで新しいサービスプロバイダのコードを生成するための最終的なWSDLとスキーマを利用して、サーバーのアプリケーションをそのコードで動作するように変更します。
著者について
Dennis Sosnoski(サイト・英語)はJava ベースのSOAやWebサービス専門のコンサルタントやファシリテーターのトレーニングを行っています。彼の専門家としてのソフトウェア開発経験は30年以上に及び、ここ9年間はサーバーサイドのXMLとJavaの技術に注力しています。Dennisは、Apache Axis2のWebサービスフレームワークのコミッターであるばかりでなく、オープンソースであるJiBX XML data binding framework(サイト・英語)の開発リーダーであり、Jibx2Wsdl tool(サイト・英語)の開発にも関わっています。さらに、JAX-WS 2.0 や JAXB 2.0のエキスパートグループのメンバーの一人でもあります。より詳しい情報が知りたければ、彼のWebサイト(サイト・英語)をクリックするか、enquiry@sosnoski.com宛にメールをお願いします。原文はこちらです:http://www.infoq.com/articles/sosnoski-code-first