レベル: 中級 W. Paul Kiel, President, xmlHelpline.com Consulting
2008年 09月 23日 リストに新しい値を追加できるという要件は、一般的な必須の要件です。スキーマの設計者はしばしば、設計時には認識していなかった値を追加できる手段をアーキテクチャーの中に組み込もうとします。値が列挙されたリストを作成する際、拡張可能で実装が容易なリストにするにはどうすればよいのでしょう。この記事では、そのための手法をいくつか学びます。
スキーマの設計者と実装者は、XML スキーマの中の既存の列挙リストを拡張するための方法を必要とします。残念ながら XML Schema 仕様では、そうしたリストを作成する際にリストに拡張性を持たせることを許していません (「参考文献」を参照)。リストに含められる値は、設計時に選択された値で固定され、それらの値しか利用することができません。こうした制約にもかかわらず、人々はさまざまな回避策を使ってリストの拡張を実現しています。私の顧客の多くは変更することができない既存のスキーマを扱っているため、リストを拡張可能にする機能を頻繁に要求してきます。彼らは後方互換性を維持しつつ、新しい機能を追加したいと望むのです。この記事では、この機能を実現するためにスキーマの設計者はどのようにして障害を克服すればよいのかを説明します。
列挙リストは、ある特定のデータ・ポイントに対して指定された一連の値です。例えば国コードは、DE (ドイツ)、US (アメリカ合衆国)、JP (日本) といった固定された値のリストと考えることができます。この値セットがある場合、例えば TL (東ティモール) や BA (ボスニア・ヘルツェゴビナ) などの新しい国が認められた場合にはどうなるのでしょう。それまでの国名リストを使用している人達は皆、これらの新しい値に対応するために実装を変更しなければなりません。
XML スキーマによってデータをモデリングする場合、列挙される値は明示的なリストにされます。そのため、国コードのリストでは順番にそれぞれの国が含まれます。スキーマの設計者達はリストへの新しい値の追加はよくあることであり、対応が必須であることを認識していたため、列挙リストを拡張するための方法を長年模索してきました。実質的にそれは、設計時には認識していなかった値を追加できる手段を設計の中に組み込もうとするものでした。
拡張可能な列挙リストを作成する
この問題に対するソリューションを見つける際には、どの方法によってリストを拡張するのかを次の 4 つの重要な基準を使って判断します。
- 第 1 に、設計が終わった後でリストを拡張できる必要があります。新規の取引相手を迅速にセットアップする場合であれ、あるいは時間的要求が厳しい新しいデータ・フィールドをセットアップする場合であれ、最後になって拡張できるという機能は現実に要求されます。
- 第 2 に、実装を容易にする上で、パーサーの中で値の妥当性を検証できる機能が重要です。
- 第 3 に、構文解析と妥当性検証が 1 つのパスの中で行えることが重要です。このため、Genericode のように妥当性検証がパーサーとは別のパスで行われるようなソリューションは除外されます。場合によっては、新しい技術要件が追加されることで、コストも時間も大幅に増大する可能性があります。
- 最後に、元のスキーマと後方互換性のあるソリューションでなければなりません。互換性のない変更をリストに加えることは拡張とは言えません。
一部の人達は絶対に列挙リストを拡張してはならないと主張します。またデータ・モデルの設計者達は、モデルにもっとデータが必要であるなら、そのモデルを大きくし、その副産物としてスキーマを設計すべきだと言うかもしれません。つまりもっと大きなモデルを作成し、その後で必要に応じて制限を加えるというわけです。これは元のスキーマとデータ・モデルを調整できる場合には妥当であり、理想的な手法かもしれません。しかし現実問題として設計後の拡張が必要な場合には、そうした方法は不可能かもしれないのです。
さらにまた別の人達は、列挙リストを拡張するための鍵は、XML スキーマの妥当性検証パーサーでは列挙リストを扱わないことだと主張します。Genericode (「参考文献」を参照) では、列挙リストの妥当性検証を 2 番目のレイヤーとし、XML スキーマのパーサーによる最初の妥当性検証プロセスには含めないように推奨しています。この理論は適切であり、この方法はやがて広く使われるようになるかもしれません。しかし 1 つのパーサー・パスで妥当性検証を行いたい場合には、このソリューションは使えません。また場合によっては 2 番目の妥当性検証パスが不可能な場合もあります。
もちろん、新しいリストを持つ新しい要素を作成することはいつでも可能です。しかしこの方法には元のスキーマとの後方互換性がありません。理想的な方法は、後方互換性を維持しつつリストの拡張を実現する方法です (「参考文献」を参照)。
この記事では、私が顧客と作業を行ってきた経験、つまり既存の列挙リストを拡張して値を追加したいという要求を前提として説明を進めます。また、それを XML スキーマ・パーサーによって行えること、そして他のすべてのことと共に 1 つのステップで妥当性検証できることも前提とします。
列挙リストを拡張するための要件
ここで行う拡張の例には、次の 4 つの要件があります。
- 列挙される値を設計が終わった後で拡張できること
- パーサーによって列挙の妥当性を検証できること
- 1 つのパスで列挙の妥当性を検証できること
- 元のスキーマとの後方互換性を維持できること
例えば、ある分野の業界コンソーシアムの列挙リスト (あるいは任意の既存リスト) を扱うチームが、そのリストを使うためにスキーマ・コンポーネントを採用していると考えてみてください。既存のスキーマが提供しているのは、列挙した値のリストを持つ MaritalStatus (結婚歴) コンポーネントです (リスト 1。
リスト 1. MaritalStatus 列挙リスト
<xsd:simpleType name="MaritalStatusEnumType">
<xsd:restriction base="xsd:normalizedString">
<xsd:enumeration value="Divorced"/>
<xsd:enumeration value="Married"/>
<xsd:enumeration value="NeverMarried"/>
<xsd:enumeration value="Separated"/>
<xsd:enumeration value="SignificantOther"/>
<xsd:enumeration value="Widowed"/>
<xsd:enumeration value="Unknown"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="MaritalStatus" type="MaritalStatusEnumType"/>
|
これらの値を使おうとしているある会社が、重要な取引相手の 1 社に対しては別の値をサポートする必要があるとしましょう。この場合の別の値 CivilUnio (同性婚) は拡張された値であり、その会社はこの値が元のスキーマにはないことを知っています。そこで結婚歴という同じ目的に対して既存の要素 (つまり MaritalStatus) を使うことにすれば、それ自体は妥当なことですが、この会社はこれをどのように処理すればよいのでしょう。
ソリューション 1: 元のスキーマを編集し、新たに列挙される値を含める
元のスキーマを編集し、新たに列挙される値を含める方法は、当然ながら最も直接的な方法です。そのスキーマのローカル・コピーを保持した上でそのスキーマを編集することで、皆さんの会社が使用する列挙された値をサポートします。
- 長所: 実行しやすい。
- 短所:
- 元のスキーマを編集する必要がありますが、元のスキーマ自体が時と共に変更されてしまい、手に負えなくなくなる可能性があります。既存のリストを拡張する場合には、その拡張の元となったところ (取引相手、コンソーシアムなど) から新たに改訂されたリストが発行されるかもしれません。そうした新しいバージョンごとに編集した内容を反映させる必要があります。
- 手動でスキーマを編集すると、不注意による編集ミスを起こしやすくなります。
元のスキーマを編集できない (あるいは編集したくない) 場合には、別の方法が必要です。
ソリューション 2: 新たな列挙リストを作成し、リストを結合する
2 番目の選択肢は、新たな列挙リストを作成し、それを元の列挙リストと結合する方法です。リスト 1 に示した元の MaritalStatus リストに対し、リスト 2 は新たに作成された列挙リストを示しています。
リスト 2. 新たに作成された MaritalStatus リスト
<xsd:simpleType name="MyExtMaritalStatusEnumType">
<xsd:restriction base="xsd:normalizedString">
<xsd:enumeration value="CivilUnion"/>
</xsd:restriction>
</xsd:simpleType>
|
2 つのリストを <xsd:union> タグを使って組み合わせます (リスト 3)。
リスト 3. リストの結合
<xsd:simpleType name="MaritalStatusType_Union">
<xsd:union memberTypes="MyExtMaritalStatusEnumType MaritalStatusEnumType"/>
</xsd:simpleType>
<xsd:element name="MaritalStatus" type="MaritalStatusType_Union"/>
|
このソリューションの場合も、やはりスキーマを編集する必要があります (つまり MaritalStatus 要素を MaritalStatusType ではなく MaritalStatusType_Union 型にします)。先ほどのソリューションほど煩わしくはありませんが、それでも手動による編集が少し必要です。
- 長所: 元の列挙リストをいじる必要がありません。
- 短所:
- 設計時にすべての値がわかっている必要があるため、遅延バインディングによるソリューションが使えません。
<xsd:union> タグをサポートする必要がありますが、ツールによってはこのタグをサポートしていない場合があります。
ソリューション 3: パターンを作成し、そのパターンを元の列挙型と組み合わせる
今度は、人の目の色という人口統計データに関する事例に切り換えましょう。リスト 4 はこのリストを示しています。
リスト 4. 人の目の色の列挙リスト
<xsd:simpleType name="PersonEyeColorType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Black"/>
<xsd:enumeration value="Hazel"/>
<xsd:enumeration value="Gray"/>
<xsd:enumeration value="Brown"/>
<xsd:enumeration value="Violet"/>
<xsd:enumeration value="Green"/>
<xsd:enumeration value="Blue"/>
<xsd:enumeration value="Maroon"/>
<xsd:enumeration value="Pink"/>
<xsd:enumeration value="Dichromatic"/>
<xsd:enumeration value="Unknown"/>
</xsd:restriction>
</xsd:simpleType>
|
次に、新しい値に対応可能なパターン (正規表現) を作成します。このパターンは単純に、前に x: が付いた任意のストリングです。x: は標準的な列挙と拡張とを区別するための記号です。リスト 5 はこのパターンを示しています。
リスト 5. 拡張を使うための正規表現
<xsd:simpleType name="StringPatternType">
<xsd:restriction base="xsd:string">
<xsd:pattern value="x:\S.*"/>
</xsd:restriction>
</xsd:simpleType>
|
最後に、このリストとパターンを <xsd:union> タグを使って組み合わせます (リスト 6)。
リスト 6. 列挙リストと拡張パターンとを結合する
<xsd:simpleType name="MyExtPersonEyeColorType">
<xsd:union memberTypes="PersonEyeColorType StringPatternType"/>
</xsd:simpleType>
<xsd:element name="PersonEyeColor" type="MyExtPersonEyeColorType"/>
|
同じノードに標準の値が入る場合と拡張された値が入る場合があります。それぞれの値は容易に分離することができ、またパーサーによって妥当性を検証することができます (リスト 7)。
リスト 7. XML インスタンスの例
<PersonEyeColor>Black</PersonEyeColor>
<PersonEyeColor>x:Teal</PersonEyeColor>
|
- 長所:
- すべてのデータに対して同じ要素を使うことができます。
- ベースとなる列挙リストの妥当性の検証はパーサーによって行われます
- 拡張された値が明確に分離されます。
- このソリューションでは新しい値に対して遅延バインディングを使うことができます。
- 短所:
- 要素の内容を解析しないと、その要素の内容が拡張されたものかどうかを判断することができません。
- スキーマのパーサーが正規表現のファセットをサポートする必要があります。
<xsd:union> タグをサポートする必要があります。
ソリューション 4: 拡張された値に対して別のフィールドを使う
このソリューションでは列挙フィールドが変更されません。ただし追加の値に対応できるように、スキーマの中に拡張フィールドを設計しておきます。このケースでは、最初のリストは被扶養者のタイプ (給与所得者と被扶養者の間の関係) です (リスト 8)。
リスト 8. 被扶養者との関係の列挙リスト
<xsd:simpleType name="DependentRelationshipEnumType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="AdoptedChild"/>
<xsd:enumeration value="Brother"/>
<xsd:enumeration value="Child"/>
<xsd:enumeration value="ExSpouse"/>
<xsd:enumeration value="Father"/>
<xsd:enumeration value="Granddaughter"/>
<xsd:enumeration value="Grandson"/>
<xsd:enumeration value="Grandfather"/>
<xsd:enumeration value="Grandmother"/>
<xsd:enumeration value="LifePartner"/>
<xsd:enumeration value="Mother"/>
<xsd:enumeration value="Sister"/>
<xsd:enumeration value="Spouse"/>
<xsd:enumeration value="Extension"/>
</xsd:restriction>
</xsd:simpleType>
|
新しい値に対応するための追加の属性 extension が必要です。リスト 9 はこの属性を示しています。
リスト 9. 被扶養者との関係のための extension 属性
<xsd:complexType name="DependentRelationshipType">
<xsd:simpleContent>
<xsd:extension base="DependentRelationshipEnumType">
<xsd:attribute name="extension" type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:element name="DependentRelationship" type="DependentRelationshipType"/>
|
リスト 10 は、この拡張を反映した XML インスタンスを示しています。
リスト 10. XML インスタンスの例
<DependentRelationship>Child</DependentRelationship>
<DependentRelationship extension="MyNewRelationship">Extension</DependentRelationship>
|
- 長所:
- 元のスキーマを編集する必要がありません。
- このソリューションでは新しい値に対して遅延バインディングを使うことができます。
- 元のスキーマの設計の中に
extension メソッドが明示的に組み込まれています。
- 短所:
- 設計段階で各列挙リストの中に
extension メソッドを組み込んでおく必要があります。
- 列挙される値は、属性としてではなく、要素の値として記述される必要があります。
ソリューション 5: ドキュメント・ベースの手法 -- ストリングによる結合
注意: ソリューション 5 と 6 は「1 つのパスでの妥当性検証」という要件に違反しています。しかし実際の多くのコンテキストで使用されているため、ここに含めています。
5 番目のソリューションでは、<xsd:union> タグを使ったストリングによって組み合わされる列挙リストを使います。このソリューションでは実質的に、組み合わされる側のシステムに対して (大文字小文字やスペルを含めて) どの値が標準なのかに関するヒントが与えられますが、実際にはストリング・フィールドにはすべての値が許可されます。そのためパーサーは値の妥当性を検証せず、2 番目のパス、あるいはそのデータを受け取るアプリケーションの中で値の妥当性が検証されます。この方法は、例えば一部の XML コンソーシアムで使われています。
リスト 11 は <xsd:string> を持つ <xsd:union> によって組み合わされる列挙リストを示しています。どのような値もストリングになりうるため、列挙されたものの妥当性は検証されず、標準的な値と見なされます。
リスト 11. ストリングによって組み合わされた DayOfWeek 列挙リスト
<xsd:simpleType name="DayOfWeekEnumType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Sunday"/>
<xsd:enumeration value="Monday"/>
<xsd:enumeration value="Tuesday"/>
<xsd:enumeration value="Wednesday"/>
<xsd:enumeration value="Thursday"/>
<xsd:enumeration value="Friday"/>
<xsd:enumeration value="Saturday"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="DayOfWeek" type="DayOfWeekEnumType"/>
<xsd:simpleType name="ExtendedDayOfWeekType">
<xsd:union memberTypes="DayOfWeekEnumType xsd:string"/>
</xsd:simpleType>
<xsd:element name="DayOfWeek_solution5" type="ExtendedDayOfWeekType"/>
|
- 長所: (遅延バインディングにおいてさえも) 無制限に値を追加して拡張することができます。
- 短所:
- 列挙される値はパーサーでは妥当性検証が行われず、2 番目のステップで行われます。
<xsd:union> タグをサポートする必要があります。
ソリューション 6: ドキュメンテーション・ベースの手法 -- <xsd:annotation> を使う
この手法を採用するためには、実際に列挙される値を <xsd:documentation> タグの中に置き、データ・フィールドは単純なストリングのままとします。リスト 12 は列挙される値を示しています。
リスト 12. <xsd:documentation> タグの中に列挙された値
<xsd:element name="DayOfWeek" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<!-- suggested enumerations -->
<xsd:enumeration value="Sunday"/>
<xsd:enumeration value="Monday"/>
<xsd:enumeration value="Tuesday"/>
<xsd:enumeration value="Wednesday"/>
<xsd:enumeration value="Thursday"/>
<xsd:enumeration value="Friday"/>
<xsd:enumeration value="Saturday"/>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
|
- 長所:
- (遅延バインディングにおいてさえも) 無制限に値を追加して拡張することができます。
- 最も単純な XML スキーマ機能しか必要ありません。
- 短所: 列挙される値はパーサーでは妥当性検証行われません。
考慮しなかった手法
列挙リストを拡張するためのソリューションとしては、まだいくつかの手法が考えられます。以下に示すのは、使われない 2 つの手法の簡単な説明です。
<xsd:redefine> タグを使う方法: XML スキーマのこの機能は通常は使われず、ツールに実装されていないことが多いものです。多くの場合、再定義を避けることがベスト・プラクティスだと考えられています。
- すべての値を含む結合リストの中で
substitutionGroup 要素を使って交換する方法: もう 1 つの使いたくなるソリューションとして、置換グループとユニオンを使う方法があります。この手法では、まず元のリストと新しいリストとを結合したものを使って、すべてを含む列挙リストを作成します。次に、substitutionGroup (または <xsi:type> タグ) を使ってグローバル・スコープの要素を置き換えます。この手法の問題点は、ユニオンによる派生を置換するのが妥当ではない点です (置換では 2 つのコンポーネントが同じ基底型から派生する必要があります)。XML Schema の仕様によれば、拡張と制限による派生はどちらも置換をするには妥当な手法ですが、ユニオンによる派生は妥当な手法ではありません (「参考文献」を参照)。
まとめ
XML スキーマの設計者や実装者は、既存の列挙リストを拡張する方法を必要としています。XML スキーマの仕様では、元のリストを作成した後で拡張することは許されていないため、現実的な実装を実現するためには何らかの回避策が必要になります。XML スキーマを実装する人は、この記事で紹介した例を活用して列挙の設計や拡張を行うことができます。それぞれの方法には長所と短所があり、どの場合においてもベスト・プラクティスという方法はありません。では、どの方法を使えばよいのでしょう。
以下の経験則を考慮してみてください。
- 元の列挙リストやスキーマを編集することに抵抗を感じず、拡張される列挙値がすべて設計時にわかっている場合には、ソリューション 1 (元のスキーマを手動で編集する) またはソリューション 2 (新しいリストを作成して元のリストと結合する) が最善かもしれません。
- 同じ意味体系の要素を使ってベースとなる列挙と拡張された列挙を含めたい場合には、ソリューション 3 (パターンによる結合) を考えます。
- リストと拡張に対して異なるフィールドを持つことを気にしないのであれば、ソリューション 4 (別のフィールドを使う) が適切でしょう。
- 列挙された値をパーサーで扱わないようにするには、Genericode による手法を検討するか、あるいはソリューション 5 または 6 を使います。
こうしたガイドラインによってスキーマ設計者は現実的で実用的なベスト・プラクティスを手にすることができ、実装が容易で拡張可能な列挙リストを作成できるようになります。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| XML schema and XML instance examples | ExtendEnumeratedListsCode.zip | 2KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
- developerWorks から直接ダウンロードできる IBM の試用版ソフトウェアを利用して皆さんの次期プロジェクトを構築してください。これらの試用版には DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品が含まれています。
議論するために
著者について  | 
|  | Paul Kiel は標準の実用化を専門とするコンサルタント会社である xmlHelpline.com の社長です。特に重点としているのは、XML スキーマの設計やベスト・プラクティス、データ・モデリング、XSLT、データ統合などです。彼は以前、HR-XML Consortium の Chief Architect の地位に就いており、1997年以来 XML 技術に関わってきています。彼は元々図書館員かつ歴史的文書保管員として訓練を受けており、趣味として歴史的なドキュメンタリーを収集しています。 |
記事の評価
|