Web サービスのヒントと秘訣: 匿名型を避ける
はじめに
匿名型を使用して Web サービスの XML スキーマが作成される場合がよくあります。スキーマの作成者は、そのようなスキーマを作成する際に引き起こされるかもしれない潜在的な問題に気付いていない可能性があります。この記事では、匿名型を使用することによって生じる可能性のある問題について、読者の皆さんがそうした問題を避けられるように説明します。
匿名型とは何か
匿名の XML 型は xsd:element に埋め込まれた XML 型です。xsd:element に埋め込まれているため、匿名の XML 型には名前がありません。例えばリスト 1 を見てください。
リスト 1. 匿名の person 型
<xsd:element name="person"> <xsd:complexType> <xsd:sequence> <xsd:element name="first" type="xsd:string"/> <xsd:element name="last" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element>
person という名前の要素の中にある complexType には名前がないため、この complexType は匿名です。この匿名型は、以下の簡単な手順で名前付きの型に変換することができます。
- complexType の定義を外に移動し、グローバル・スコープにする
- complexType に name 属性を指定する
- このグローバル要素に対し、新たに名前が付けられた complexType を参照する type 属性を指定する
この手順に従ってリスト 1 の匿名型を名前付きの型に変換したものがリスト 2 です。
リスト 2. 名前付きの Person 型
<xsd:element name="person" type="Person"/> <xsd:complexType name="Person"> <xsd:sequence> <xsd:element name="first" type="xsd:string"/> <xsd:element name="last" type="xsd:string"/> </xsd:sequence> </xsd:complexType>
リスト 1 とリスト 2 のスキーマは論理的に同じです。どちらのスキーマを使用しても、リスト 3 の XML インスタンスを妥当性検証することができます。
リスト 3. 名前付きの Person 型
<person> <first>John</first> <last>Doe</last> </person>
匿名型では何が不都合なのか
これから先の説明にはすべて、リスト 4 とリスト 5 のスキーマを使用します。リスト 4 は名前付きの型を 1 つも定義していません。すべての型は匿名型です。比較のために、リスト 5 に名前付きの型と等価なスキーマを示します。リスト 5 は上記の手順をリスト 4 に適用して作成したものです。
リスト 4. 匿名型で定義された account 要素
<xsd:element name="account"> <xsd:complexType> <xsd:sequence> <xsd:element name="number" type="xsd:int"/> <xsd:element name="person"> <xsd:complexType> <xsd:sequence> <xsd:element name="firstName" type="xsd:string"/> <xsd:element name="lastName" type="xsd:string"/> <xsd:element name="otherNamesOnAccount" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="person"> <xsd:complexType> <xsd:sequence> <xsd:element name="firstName" type="xsd:string"/> <xsd:element name="lastName" type="xsd:string"/> </xsd:sequence> </xsd:complexTypeb> </xsd:element> <xsd:element name="relationshipToAccountOwner" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element>
リスト 5. 名前付きの型で定義された account 要素
<xsd:element name="account" type="tns:Account"/> <xsd:complexType name="Account"> <xsd:sequence> <xsd:element name="number" type="xsd:int"/> <xsd:element name="person" type="tns:AccountOwner"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="AccountOwner"> <xsd:sequence> <xsd:element name="firstName" type="xsd:string"/> <xsd:element name="lastName" type="xsd:string"/> <xsd:element name="otherNamesOnAccount" minOccurs="0" maxOccurs="unbounded" type="tns:OtherNameOnAccount"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="OtherNameOnAccount"> <xsd:sequence> <xsd:element name="person" type="tns:Person"/> <xsd:element name="relationshipToAccountOwner" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="Person"> <xsd:sequence> <xsd:element name="firstName" type="xsd:string"/> <xsd:element name="lastName" type="xsd:string"/> </xsd:sequence> </xsd:complexType>
なぜ私達がリスト 4 の匿名型を好まないかと言えば、以下の理由が挙げられます。
- 再利用することができません。
- 匿名型であっても、やはり多くの場合は名前が必要です。
- リスト 4 はリスト 5 ほどスマートではありません。
この記事のこれから先では、この 3 つのポイントそれぞれの詳細について説明します。
再利用することができません
これは匿名型の一番の問題です。ある要素の中で定義された匿名型は、その要素のみで取り得る型となります。他の場所にある別の要素を同じ型にしたい場合、最も良い方法は、その匿名型の定義を切り取って新しい要素に貼り付ける方法です。
例えば、リスト 4 では firstName/lastName というペアの要素が重複しています。1 つのペアは外側の person 要素の中にあり、もう 1 つのペアは内側の person 要素の中にあります。この 2 つの要素を再利用できるようにするためには、この 2 つの要素を独自の名前付きの型の中に配置する必要があります。そうした名前付きの型がなければ、2 つの要素は単にスキーマ内のある場所から別の場所へとコピーされます。
リスト 5 には、そうした名前付きの型、Person があることに注意してください。リスト 5 は名前付きの型が使われていることを除けばリスト 4 と等価であり、XML インスタンスの構造に影響するような変更は何もしていません。しかし今度は Person という名前付きの型があるため、AccountOwner の中で Person 型を再利用することで、リスト 5 のスキーマをさらに改善することができます。
匿名型であっても、やはり多くの場合は名前が必要です
再利用について気にしない場合であっても、匿名型を使用すると、やはり潜在的な危険性があります。XML スキーマの使い方を考えてみてください。例えば Web サービスを定義する場合、XML スキーマがプログラミング言語にマッピングされる可能性が十分にあります。その場合、その言語の中で型が匿名のまま、とはいかない可能性があります。何らかの方法で匿名型に名前を付ける必要があり、その名前は、その型を包含するスキーマに基づくものでなければなりません。どのようなルールでスキーマを基にするにせよ、そのルールが破綻するシナリオがあり得ます。
例えば JAXB 仕様によって XML スキーマを Java にマッピングするというルールの場合、そのルールはリスト 4 で定義されるスキーマでは破綻します。JAXB では、匿名型のクラスの名前は、その型を包含する要素を基にする、と定義されています。また、XML に埋め込まれた匿名の複合型は Java の内部クラスになります。JAXB によれば、リスト 4 の XML スキーマは以下の Java クラスにマッピングされます。
- Account
- Account.Person
- Account.Person.OtherNamesOnAccount
- Account.Person.OtherNamesOnAccount.Person
Java では、ネストされたクラスは、そのクラスを包含するクラスと同じ名前を付けることができません。同じ名前を付けると、コンパイル時に「The nested type Person cannot hide an enclosing type (ネストされた Person 型は、その型を包含する型を隠すことができません」といったようなエラーが表示されます。
私達がこの問題を解決する方法は、リスト 5 で行ったように、すべての型に名前を付ける方法です。つまりご存知のように、大抵は Java 型の名前はその要素を包含する要素の名前を基にするという、JAXB で使われている名前の付け方を適用します。ただし、そのルールをすべての型に適用しているわけではありません。そのルールをすべての型に適用すると、2 つの Person 型ができてしまうことになります。そのため、ここでは最初の Person 型を AccountOwner にリネームしています。
JAXB バインディング・ファイル
私達にとっては JAXB バインディング・ファイルを用いた解決方法が好ましいのですが、この方法では XML スキーマを変更する必要があります。スキーマを変更できない場合のために、JAXB には別の解決方法が用意されています。JAXB では、XML から Java へのマッピング方法を JAXB エンジンに指示するバインディング・ファイルをユーザーが作成できるようになっています。JAXB 仕様を作成した人達は、仕様に規定されたマッピングがすべての人にとってあらゆる状況で有効なわけではないことを認識していました。そのため彼らは、標準とは異なる場合のためにこのメカニズムを用意したのです。リスト 6 のバインディング・ファイルは JAXB 生成プログラムに対し、最初の Person 要素の下にある匿名の complexType 型から生成されたクラスを変更し、”AccountOwner” という名前にするように指示しています。
リスト 6. リスト 4 の問題を修正するために必要な JAXB バインディング・ファイル
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsi="htp://www.w3.org/2001/XMLSchema-instance" mlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd" jaxb:version="2.1"> <jaxb:bindings schemaLocation="Anonymous.xsd" node="/xsd:schema /xsd:element[@name='account']/xsd:complexType/xsd:sequence /xsd:element[@name='person']/xsd:complexType"> <jaxb:class name="AccountOwner"/> </jaxb:bindings> </jaxb:bindings>
内側の jaxb:bindings 要素は schemaLocation 属性を含み、この属性が JAXB エンジンに対し、このバインディング・ファイルの影響を受けるスキーマはどれかを指示しています。またこの要素は node 属性を含み、その値は特別なバインディングを適用する対象となる要素の XPath 式です。そして jaxb:class 要素は、指定された XPath の要素から生成された JAXB クラスの名前が ”AccountOwner” となることを表しています。JAXB マッピング・ファイルについての詳細は「参考文献」を参照してください。
JAXB は非常に堅牢であり、必要な場合にはバインディング・ファイルを使用することで匿名型に名前を付けることができます。ただしそうすると、サービスを構築するためのプロセスが複雑になります。また、すべての言語のマッピングで JAXB と同じレベルの機能が利用できるとは限りません。そのため、スキーマを変更できる場合には、匿名型を避けることが最善の方法です。
あまりスマートではありません
リスト 4 に対する私達の最後の不満は、リスト 5 ほどスマートではないことです。匿名型を好まない最後の理由は純粋な意見にすぎないのは確かですが、私達の意見では、匿名型は名前付きの型ほど読みやすくありません。深くネストされた XML 構造はほとんどの場合、そのまま即座に理解することは困難です。匿名型を使用した構造は名前付きの型を使用した構造に比べ、扱うのが困難です。
まとめ
以下のようないくつかの理由から、匿名型を避ける必要があります。
- 再利用することができません。
- マッピングによって匿名型に名前を付ける必要がある場合、問題を引き起こす可能性があります。
- 名前付きの型ほどスマートではありません。
これらの問題は、匿名型を名前付きの型に変換することで修正することができます。JAXB によって XML スキーマを Java にマッピングする場合、スキーマを変更できない場合には、JAXB エンジンに JAXB バインディング・ファイルを提供することで、起こりうる問題を軽減することができます。
ダウンロード可能なリソース
関連トピック
- JAXB 2.0 仕様
- Oracle の Java Web Services Tutorial の JAXB バインディング・セクション
- XML Schema Part 1 と Part 2