XMLによるメッセージ交換は、SOAPやRESTといった手法を含む、数多くのWebサービスのほとんどにおいてその基礎となっています。XMLを使用すると、パフォーマンスに関する潜在的な問題などのトラブルが発生することもありますが、交換に関与する関係者間で疎結合を可能とする抽象化レベルも提供します。しかし、その疎結合を実際に機能させるには、妥当な文書の検証を可能とする方法で交換されるよう、XML文書の構造を定義できなければなりません。W3CのXMLスキーマ定義言語(本稿ではこれ以降、単に「スキーマ」と呼ぶことにします)は、これらのメッセージ構造体定義に最も広く使用されている手法です。
ほとんどのWebサービス・アプリケーションは、直接、XML文書と連動することはなく、代わりにWebサービス・ツールキット内のデータ・バインディング変換層を経由します。これは、アプリケーション開発者にとって都合がよいのですが、それは、自ら選択したプログラミング言語でデータ構造に直接処理を行えることを意図しているからです。しかし、データ・バインディングのステップは、スキーマのデータ型と構造およびプログラミング言語のデータ型と構造間のミスマッチを処理する必要があり、さらに、このミスマッチによりアプリケーションに不具合が生じる可能性があります。Webサービスに一貫性や、クロスプラットフォームの互換性(一般に、そもそもこの点がWebサービス利用の本質です)を持たせたければ、潜在的な問題点を回避する (あるいは、少なくとも問題となるスキーマ機能の使用に伴うリスクを認識する) ようなスキーマ定義の設計が必要となります。
この一連の記事では、スキーマとWebサービスのデータ・バインディング間の不整合から生じるさまざまな問題を取り上げます。最初の記事では、最も基本的なレベルから始めることとし、単純なデータ型とそこから生じる問題を考察します。
数値表現
業務データといえば、数値が得られるものですが、これくらい数値というものは基本となるものなのです。数値の重要性を考慮すると、これは、スキーマがスムーズかつ一貫性をもって機能する点になると考えられるかもしれません。事実、抽象的な意味ではそうなのです。しかし、スキーマがWebサービス・ツールキットにより適用されると、さらに多くの問題に遭遇する可能性があります。
この問題の一部は、スキーマの組み込み数値データ型が、実に多種多様である点です。図1は、この点に関するスキーマ・データ型の一部をツリー構造で表しています。これを理解するには、特殊化の観点で考察します。つまり、上下逆のツリーにおける、ブランチの一つを下方へ移動するにしたがい、型で表されるデータは特殊化されていきます。最上層、つまり汎用的なanySimpleTypeの直下には、3つの基本数値型であるfloat、decimal、doubleがあります。floatおよびdoubleはterminal型で、IEEE標準規格に準拠した浮動小数点数です。このため、Webサービス・プラットホーム全体で、優れた相互運用性を提供します。つまり、あらゆる主要プログラミング言語において、floatの仕様に準拠した32ビット浮動小数点数、およびdoubleのスキーマ仕様に準拠した64ビット浮動小数点数をサポートしています。したがって、Webサービス・ツールキットは直接これらの型をネイティブ言語の型にマップすることができます。特殊値(非数値、正負の無限数、および正負の0)において、プログラミング言語の文字列表現とスキーマで使用する表現に、若干の違いが生じる場合がありますが、ツールキットにより変換処理が容易に行えます。
Figure 1. Schema numeric types
問題に遭遇し始めるのは、ツリーのdecimalブランチを下方へ移動する際です。Decimalそのものは、あらゆる任意の先行符号および小数点付き10進数値の文字列として定義されています。integerはdecimalから直接派生したもので、あらゆる10進数値を許容する点において、decimalに相当する値のサブセットに対応します。任意の先行符号付き10進数値は許可されていますが、小数点は許可されていません。integerの派生はさらに取り得る値が制限されます。NonPositiveIntegerとnonNegativeIntegerは、値をそれぞれ0より大きいまたは0未満と制限しており、longでは値の範囲が64ビットの2の補数と同値に制限されています。int、shortおよびbyteは、それぞれ32ビット、16ビットおよび8ビットの2の補数へと、さらに範囲が制限されます。unsignedの派生は同じビット数の符号なし数値に対応します。
あらゆる主要プログラミング言語は、スキーマ型のlong、intおよびshortに対応する値をツリーのメイン・ブランチに沿ってサポートしますが、その他の派生形では潜在的な問題が生じます。例えば、JavaにはunsignedLongやunsignedIntに対応するプリミティブ型が組み込まれていません。通常、JavaのWebサービス・フレームワークでは、これらのプリミティブ型ではなく特殊クラスを使用し、この言語サポートの欠如に対処しています。しかし、これにより、Webサービス・インタフェースは、多少使い勝手が悪く、パフォーマンス問題を引き起こす可能性があります (通常、プリミティブ型はオブジェクト型より演算時の処理がはるかに高速であるためです)。
decimalやinteger型でさえ問題を提示します。大半のJavaツールキットは、標準のjava.lang.BigDecimalおよびjava.lang.BigIntegerクラスを使用することで、この問題に対処しています。これはパフォーマンスの劣化を招きますが、サイズ制限のない値をサポートします。.Netでは、代わりに固定サイズの128ビット表現を使用し、取り得る値の範囲 (スキーマ仕様で認められている通り) を制限しますが、比較的良好なパフォーマンスを提供します。
スキーマ数値型は、混乱しやすく一貫性がありません (例えば、なぜnonPositiveDecimal型ではなくnonPositiveInteger型なのでしょうか?)。また、スキーマ数値型は、通常、いかなるケースでも、単にシンタックス・シュガーを示しています (この数値型の範囲は代替としてsimpleTypeの制限を用いて実装可能なためです)。これらの理由から、スキーマ定義、とりわけWebサービスでの使用を目的としたものでは、これらの型の大半を使用しないようにするのが最善です。可能であれば、サイズ指定のある型を使用します (実数にはdoubleやfloat、整数にはlongやint)。これらの変換は、一貫してプログラミング言語のプリミティブ型へと解釈するためです。このサイズ制限のある型で、範囲や有効桁数を超える値を処理する必要がある場合、実装の相違により、decimalやintegerからは必ずしも期待したものが得られないため、代替として文字列の使用や、アプリケーション・コードによる値の変換処理を考慮したほうがよいと理解しておいてください。
時間に関する問題
時間関連の値は、スキーマで処理する際、別の問題を引き起こす原因として知られています。時間に関する9つの異なるデータ型がスキーマにより定義されています。すべて、特定の西洋グレゴリオ暦のバージョンがベースとなっています。数値型とは異なり、時間関連の型は特殊化関係におけるいかなる直接形式にもありません。代わりに、すべて汎用的なanySimpleTypeから直接派生すると考えられています。
最も広く使用されている時間データ型はdateTime、date、およびtimeです。この3つのデータ型は共通の表記フォーマットを共用しており、dateTimeが汎用ケースです。dateTime値の例を一つ挙げます。本稿執筆時の現在時刻は「2008-09-08T15:38:53」です。date値はdateTime型と同一表記を使用しますが、「T」および後続の時分秒の値を取り除きます (この場合「2008-09-08」を残します)。time値は対照的に「T」まですべてを取り除き、時分秒の値 (「15:38:53」) のみを残します。
ここまでは、実に単純に見えますね?混乱が生じるのは、これらの値の1つを実際に解釈する際です。Dateとtimeは現在位置する場所により変化し、通常、時間帯により表記が変化します。例えば、本稿はニュージーランドで執筆しており、現在、世界時より12時間、太平洋標準時のアメリカ西海岸より事実上、19時間進んでいることになります。同時に、例に挙げた時刻をdateTime値で記述すると「2008-09-08T15:38:53」となり、シアトルの時刻は「2008-09-07T20:38:53」となりました。
多くのアプリケーションでは、date/timeはある値を別の値と関連付けられるよう指定する必要があります。スキーマは、付加された時間帯表記をdate/time値が使用できるようにすることでこの必要要件をサポートします。この時間帯表記は文字「Z」の形式を取ることも可能で、協定世界時 (UTC) のdate/time値の表記、あるいは、協定世界時の時間と分のオフセットに使用します。このため、これらdateTimeのいかなる値 (あるいは、さらに多くの変形) もすべて同時刻の表記 (「2008-09-08T15:38:53+12:00」、「2008-09-07T20:38:53-08:00」 または 「2008-09-08T03:38:53Z」) として使用できます。
一方、スキーマには、時間帯表記を指定する必要がありません。そのような表記がなくとも、date/time値は、世界中どこであってもある任意の場所に、正確な解釈をすることしかできません。一部のアプリケーションにとって、これはまさに目的に合うものかもしれません。例えば、ある人の生年月日は通常、場所に関係なく特別な日付として処理されます。同様に、人々がグレゴリオ暦の新年を祝う際、それは世界のその場所で行われます。しかし、中には深刻な問題を引き起こすアプリケーションもあります。例えば、電話会議のケースを考えてみます。この場合、全関係者はイベントの時間を現地の時計に合わせる必要があります。
残念ながら、スキーマでは、ケースに応じてそれが完全に指定されたdate/timeを必要としているのか、また、時間帯なしの値が許可、または想定までもされているのか、といったことを識別できません (少なくとも、Webサービス・ツールキットが解釈可能な方法ではできません。これはsimpleTypeの制限パターンを利用することで対応可能ですが、通常、ツールキットはパターンを考慮しないためです)。したがって、この点におけるスキーマのあいまいさは、ツールキットが時間帯表記有無の両値を処理する必要があることを意味します。
両タイプの値を処理する必要性は、解釈面で大きな悩みの種となります。これは特に、プログラミング言語が一般に、date/timeを実装しており、絶対時刻の値に基づいて処理することに起因しています。時間帯表記が欠落しているスキーマ値を正確に絶対時刻へと変換する方法はありません。いずれにせよ、これによりツールキットがそのような値に対し、何らかの処理を行うのを阻止するわけではないことは当然の成り行きです。ほとんどの場合、ツールキットは、現地の時間帯が与えられたと仮定することでその値を変換し、それが期待値であることが多いのです。しかし、そうでない場合、結果として生じる問題点の切り分けは非常に困難なものとなるでしょう。
時間帯に起因する問題は、とりわけdate型に関して厄介なものとなります。ほとんどの場合、日付はカレンダー上で固定スロットとして処理されます。例えば、法的文書に署名する場合、通常、署名日を記入します。新しいプロジェクトに合意する場合、通常、完了予定日があります (この予定日は非現実的な場合もありますが)。また、買い物の際、年齢証明に運転免許証の提示を求められた場合、店員は生年月日を見て、年齢制限と比較するでしょう。これらすべてにおいて、日付は確定日とみなされ、時間帯による差異は、通常、考慮されません。しかし、スキーマのdate型は、dateTime型やtime型と同様、付随する時間帯表記を使用します。この時間帯表記の使用により、スキーマのdate型と一般形式の日付との間で乖離が生じます。通常、日付は、いかなる時間帯が指定されていたとしても、時刻はその日の始まりを表す00:00 (一日の始まりとして午前0時) に変換し処理しています。しかし、現地の時間帯を用いてその日付の値を印刷した場合、当初、文書で明記したものと異なることに気が付くでしょう。
スキーマがdate/time値の型を時間帯仕様の有無を用いて個別に定義可能であれば、アプリケーション側でどの型を使用すべきか選択するのは容易になるでしょう。この機能がなければ、スキーマ内で根本的に欠陥のあるdate/time値の表現をツールキット側で処理するのは困難です。JavaのJAXB 2.0はこの問題に対し、おそらく最も理解し易いアプローチを取っており、すべてのスキーマdate/time型を特殊クラス (javax.xml.datatype.XmlGregorianCalendar
) を用いて処理します。このクラスは、スキーマ表現にまさに一致するものです。このアプローチは、スキーマが表現する値の微妙な差異をすべて保持しますが、解釈問題を開発者に押し付けるという犠牲を払っています。他のツールキットでは、一般に現地時間帯を前提とするなど、単にデフォルト値が使用されています。
この部分に潜在する厄介な問題があるとすれば、最善の普遍的アプローチは、おそらく、時間帯表記を用いて完全に指定すべき値にはスキーマのdate/time型を用いることに限られるでしょう。また、生成するいかなる文書にも、時間帯表記が含まれているか確認することです。大半のWebサービス・ツールキットでは、出力時に自動で時間帯表記を生成します。したがって、この最後に挙げた点は容易なことです。入力文書にも時間帯表記を使用する必要がある場合、より困難なものになる可能性があります。複数の処理工程を経る可能性がある文書はなおさらです。誤った仮定による変換が原因で発生する問題に遭遇しないことを確実にしたいのであれば、スキーマ表現ではstring型を使用することが最善策となるでしょう。これにより、Webサービス・ツールキットは、値解釈の試行なしでアプリケーション・コードに値を渡します。
時間帯のないdate/time値が必要な場合 (生年月日の例と同様)、ここでも、最善のアプローチは、スキーマ表現でstring型を使用することになるでしょう。スキーマ内での正確な日付表現を提供するという観点から見れば、決して十分ではありませんが、Webサービス・ツールキットが時間帯に分割されていない値を現地時間帯であると解釈する問題を回避します。
参照
アプリケーション内部で使用するデータ構造には、相互参照や間接的な関連など、コンポーネント間で複数のリンクを含むことが多々あります。一方、XMLは本質的にツリー構造がとられているため、XMLの制御を通じて一対多の関係を表すのは非常に容易です。しかし、それ以外の種類の関係には問題があります。一対多の関係でさえ十分なものとは言えないかもしれません。例えば、顧客の注文履歴をリストする文書を例に考えてみましょう。各注文では、請求先住所と発送先住所が関連付けされていますが、たいていの場合、これらの住所は1つの注文から次の注文へと繰り返されることになります。この2つの住所を各注文情報に埋め込んだ場合、文書には冗長な情報が多く含まれることになります。
参照はXMLのツリー構造による制限を回避するのに使用できます。参照の概念は、一意識別子など、あるものをXML文書内で一度、定義するということです。他のデータがそのような定義の使用を要する時は、常に、一意識別子を用いた参照を作成します。
スキーマは2つの形式の参照を直接サポートしています。1つ目は、ID型を使用する方法です。IDREF やIDREFS型を用いて、文書内のどこからでもリンク可能な要素の識別子を定義します。ID/IDREFリンクが見事なのは、それがシンプルである点にあります。識別子は単なる名前であり、スキーマ内では、あらゆる種類の要素がID値を定義できます。ID/IDREFリンクのマイナス面は、グローバル・コンテキストを使用する点にあります。このため、あるIDREFに使用する値は特定要素の型に定義されなければならないとしか言いようがありません。また、ID値として使用される名前は、文書内で一意でなければなりません (複合型の要素でさえ)。Webサービス・ツールキットの中には、データ構造内で参照を表すためにID/IDREFリンクの使用をサポートしているものもありますが (JAX-WS/JAXB 2.0およびApache Axis2をJiBXデータ・バインディングと使用した場合など)、サポートしていないものもあります (.NetおよびAxis2をADBと使用した場合など)。代替として、IDREF値を単純なテキスト文字として処理します。
スキーマがサポートする2つ目の参照型は、key/keyrefリンクです。ID/IDREFリンクがデータ型を用いて定義されるのに対し、key/keyrefリンクはスキーマ構造体定義の代替部分です。この違いにより、key/keyrefリンクはID/IDREFリンクよりいっそう表現に富んでおり、キー値が一意であるコンテキストの定義を含んでいます。しかし、key/keyrefリンクは、構造化というよりは文書の妥当性検査を目的として設計されているため、複雑になっており一般に、データ構造間でXMLデータの変換を行うデータ・バインディング・フレームワークでは使用されません。
したがって、XML文書内にリンクを埋め込み、Webサービス・ツールキットで処理することを目的とした場合、ID/IDREFによるアプローチが唯一の望みとなります。ツールキットの中にはこれらのリンクを直接サポートするものもありますが、識別子の値を単に文字列として処理するだけのものあります。しかし、識別子や参照値を相互参照し、独自のリンクを作成するよう、アプリケーション・コードを書くことが可能です。
結論
本稿では、最も一般的なスキーマ・データ型をWebサービスで使用する際に生じる問題についていくつか取り上げました。本稿で触れた以外にも、スキーマに特化したデータ型は多くあり(合計42個あります)、中には別の問題を引き起こすものもあります。一般原則として、Webサービスのスキーマ定義に取り入れるべき最善のアプローチは、値の解釈の完全制御を必要とした場合、過度に特殊化された型 (一般的なプログラミング言語の型に一致する数値型を除く) の使用を回避し、文字列型を使用することです。
本稿で論じた問題のいくつかは、データ・バインディング・フレームワークにより、処理の改善が見込まれるものの、多くの問題がスキーマそのものに存在するという点は、注目に値します。特にdata/time系の型は、処理が煩わしいだけであればまだしも、最悪の場合、型値の時間帯有無の識別が欠如することで、エラーを招くこととなります。JAXBでXmlGregorianCalendar型を処理するなど、ユーザに混乱を押し付けることも可能ですが、それでは真のソリューションとは言えません。
著者について
Dennis Sosnoski氏(メール)は、JavaベースのSOAやWebサービス専門のコンサルタント、およびファシリテータのトレーニングを行っています。プロとしてのソフトウェア開発経験は30年以上に及び、ここ10年間ではサーバサイドXMLやJavaテクノロジーに力を注いできました。Dennis氏は、Apache Axis2 Web services frameworkのコミッターであるばかりでなく、オープンソースのJiBX XML data binding toolの開発リーダであり、JiBX/WS web services frameworkの開発にも関わっています。さらに、JAX-WS 2.0やJAXB 2.0の仕様におけるエキスパート・グループの一員でもあります。トレーニングやコンサルティング・サービスに関する情報は、氏のWebサイトhttp://www.sosnoski.co.nz(リンク)にてご確認ください。