! ?

Разработка расширяемого и удобного в сопровождении формата на основе XML

Автор: Адриан де Йонг, С. Е. Слэк
Опубликовано: 09.07.2010
Версия текста: 1.1

Пример простого, но проблемного формата XML
Быстрое решение: модули
Остальные преимущества модулей
Управление модулями на практике
Рискованные подходы к описанию схем на практике
Проектирование более общего формата
Управление версиями и документирование XML-схем
Немного о расширениях
Заключение

В последние 10 лет XML получил широкое распространение в качестве стандарта для хранения и обмена данными, причем как внутри одной организации, так и между несколькими компаниями. Сам по себе XML является не более чем абстрактным языком, поэтому то, насколько удачным окажется его применение, полностью зависит от конкретного XML-формата, спроектированного в той или иной организации (или группе организаций). Как и в случае с любыми программными продуктами, сопровождение XML-формата может оказаться проблематичным в условиях изменения бизнес-требований. Более того, проблемы заключаются не только в самой необходимости внесения изменений; например, часто возникает ситуация, при которой используемый формат должен синхронно модифицироваться в нескольких компаниях вследствие конкуренции и иных рыночных причин.

ПРИМЕЧАНИЕ

Часто встречающиеся аббревиатуры

NXD: Native XML database (естественная база данных XML)

XSD: XML Schema Definition (определение XML-схемы)

XSLT: XML Stylesheet Language Transformation (язык стилей для преобразования XML)

W3C: World Wide Web Consortium (консорциум WWW)

XML: Extensible Markup Language (расширяемый язык разметки)

Сопровождение одной XML-схемы, как правило, не представляет особых трудностей. Однако внесение изменений, затрагивающих несколько сотен организаций, может привести к серьезным последствиям. Поэтому даже небольшое редактирование XML-схемы может потребовать огромных временных и финансовых затрат, но если бы формат был изначально спроектирован правильно, то для подобного изменения потребовалось бы просто внимательно взглянуть на пример данных. В статье будут рассматриваться два основных вопроса:

В статье будут приведены упрощенные примеры форматов для представления данных об автомобилях, шинах и ветровых стеклах, а также их производителях и дистрибьюторах. Несмотря на сделанные упрощения, данных примеров вполне достаточно для рассмотрения способов улучшения сопровождаемости XML-форматов.

Пример простого, но проблемного формата XML

ПРИМЕЧАНИЕ

Об XML-схемах

XML-схема (XML Schema) – это основанный на XML язык, предназначенный для строгого описания формата XML-документов. Схемы могут использоваться XML-процессорами для автоматической проверки корректности документов. Другими словами, можно установить, удовлетворяет ли данный документ формату, описанному в XML-схеме. Предшественником XML-схемы является язык описания типа документов (Document Type Definition – DTD), который и по сей день используется для проверки HTML-страниц. Одним из отличительных свойств XML-схемы является то, что она сама выражается в XML, т.е. в принципе этот язык можно использовать даже для описания самого себя. Существуют и другие языки для описания XML-схем, например, RELAX NG. Применительно к XML-схемам также часто используется аббревиатура XSD (XML Schema Definition).

Документ XML может считаться корректным (или валидным) только в том случае, если он удовлетворяет заданной XML-схеме.

В качестве первого примера рассмотрим файл XML, содержащий данные об автомобиле Volvo C30 с резиной Michelin (листинг 1). Данный формат был создан для обмена информацией об автомобильных шинах.

Листинг 1. Пример простого формата XML, служащего для обмена данными о шинах
        
<car>
   <brand>Volvo</brand>
   <type>C30</type>
   <kind>Small family car</kind>
   <tires>
      <tire>
         <brand>Michelin</brand>
         <type>Winter</type>
         <count>4</count>
      </tire>
      <tire>
         <brand>Michelin</brand>
         <type>Spare</type>
         <count>1</count>
      </tire>
   </tires>
   <windscreen count="1">
      <brand>Car glass</brand>
   </windscreen>
</car>

Этот документ XML выглядит совсем несложным – с первого взгляда можно и не заметить потенциальных проблем. Однако если копнуть глубже, то их можно выявить в XML-схеме, которая представляет собой длинный документ, обладающий однородной структурой. Обратите внимание, что данный формат включает в себя предельно ограниченное число типов элементов. Только представьте себе, как бы выглядела схема для более реалистичного формата, например, представленного в листинге 2.

Листинг 2. XML-схема, описывающая формат документа из листинга 1
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="car">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="brand">
          <xs:sequence>
            <xs:element ref="type"/>
            <xs:element ref="kind"/>
            <xs:element ref="tires"/>
            <xs:element ref="windscreen"/>
          </xs:sequence>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="kind" type="xs:string"/>
  <xs:element name="tires">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tire"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="tire">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="brand">
          <xs:sequence>
            <xs:element ref="type"/>
            <xs:element ref="count"/>
          </xs:sequence>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="count" type="xs:integer"/>
  <xs:element name="windscreen">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="brand">
          <xs:attribute name="count" use="required" type="xs:integer"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="brand">
    <xs:sequence>
      <xs:element ref="brand"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="brand" type="xs:string"/>
  <xs:element name="type" type="xs:NCName"/>
</xs:schema>

Казалось бы, в чем проблема, ведь приложения не работают напрямую с XSD (файлами XML-схем)? Однако представьте, что в подобную схему придется вносить изменения вследствие изменившихся бизнес-тре­бо­ваний. Например, допустим, что информация о резине должна представляться в следующем формате:

[...]
<tire>
   <brand>Michelin</brand>
   <type>Winter</type>
   <count>4</count>
   <size>20"</size>
</tire>
[...]

В результате подобных незначительных, на первый взгляд, изменений в представлении данных о шинах перейти на новую XML-схему придется компании-производителю ветровых стекол. Кроме того, будет необходимо внести соответствующие изменения в программное обеспечение для корректной работы с новым форматом. Это весьма неприятная ситуация, так как влечет за собой лишнюю работу и затраты для компании. Производителям шин, разумеется, тоже придется обновить схему, а, возможно, и соответствующее ПО в зависимости от того, как оно было спроектировано. Таким образом, несмотря на то, что никто непосредственно не работает с файлами XSD, необходимость их корректирования может легко вылиться в серьезную проблему для большого количества людей.

Быстрое решение: модули

Появления громоздких файлов XSD можно избежать путем введения отдельных пространств имен для представления данных о шинах и ветровых стеклах. Это позволит разделить файл схемы на две части (листинг 3).

Листинг 3. Пример включения пространств имен в документ XML
[...]
   <tr:tires>
      <tr:tire count="4">
         <tr:brand>Michelin</tr:brand>
         <tr:type>Winter</tr:type>
      </tr:tire>
      <tr:tire count="1">
         <tr:brand>Michelin</tr:brand>
         <tr:type>Spare</tr:type>
      </tr:tire>
   </tr:tires>
   <wnd:windscreen count="1">
      <wnd:brand>Car glass</wnd:brand>
   </wnd:windscreen>
[...]

Введя пространства имен и оставив остальные части XML без изменения, вы значительно сократите XML-схему и упростите работу с ней. Немалая часть описания будет располагаться в дополнительных файлах XSD, которые будут импортироваться в главном файле схемы. В свою очередь, главный файл XSD приобретет модульную структуру (листинг 4).

Листинг 4. Модифицированный вариант XML-схемы, включающий отдельные модули для шин и ветровых стекол
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      elementFormDefault="qualified" 
      targetNamespace="http://car.org/car"      
      xmlns:tr="http://car.org/tire" 
      xmlns:wnd="http://car.org/windscreen" 
      xmlns:car="http://car.org/car">
 <xs:import namespace="http://car.org/tire" schemaLocation="tr.xsd"/>
 <xs:import namespace="http://car.org/windscreen" schemaLocation="wnd.xsd"/>
 <xs:element name="car">
   <xs:complexType>
         <xs:sequence> 
               <xs:element ref="car:brand"/>
               <xs:element ref="car:type"/>
               <xs:element ref="car:kind"/> 
               <xs:element ref="tr:tires"/>
               <xs:element ref="wnd:windscreen"/>
         </xs:sequence>
   </xs:complexType>
 </xs:element>
 <xs:element name="brand" type="xs:NCName"/>
 <xs:element name="type" type="xs:NCName"/>
 <xs:element name="kind" type="xs:string"/>
</xs:schema>

На данный момент схема включает всего один модуль. На самом деле XSD-файл пока сократился не столь значительно, как показано в листинге 4, но гораздо важнее то, что разбиение схемы на модули упрощает ее сопровождение и развитие. Например, вы можете изменить формат представления шин и распространить его среди всех компаний-производителей шин, не затрагивая при этом формат XML, с которым работают производители ветровых стекол.

Остальные преимущества модулей

Модульный принцип построения схем хорош не только тем, что упрощает сопровождение и распределенное использование схем. Благодаря ему появляется возможность повторно использовать отдельные элементы схемы. Например, допустим, что кроме автомобильных шин компания также производит шины для велосипедов. Вполне вероятно, что они захотят использовать тот же XSD-файл для описания данных о велосипедных шинах, что и для автомобильных. Однако компании-покупателю велосипедных шин совершенно необязательно видеть XML-схему, описывающую автомобили, так как большинство элементов в ней, в частности, ветровые стекла, не имеют никакого отношения к велосипедам. Значительно удобнее поддерживать собственную XML-схему, описывающую велосипеды, импортируя в нее XSD, определяющую формат данных о шинах.

В этом случае документы XML будут выглядеть подобно тому, как показано в листинге 5.

Листинг 5. Пример документа XML с информацией о велосипедах, в котором используется тот же формат представления данных о шинах
<bicycle>
[...]
   <tr:tire count="2">
      <tr:brand>Gazelle</tr:brand>
      <tr:type>Race</tr:type>
      <tr:size>25"</tr:size>
   </tr:tire>
[...]
</bicycle>

Этот формат похож на XSD для описания автомобилей, в котором импортировался XSD для описания шин, но в данном случае информация относится к велосипедам. В следующем разделе мы вернемся к примеру с автомобилями.

Управление модулями на практике

В конечном счете, центральный XSD-файл автомобилей будет импортировать не только XSD-файлы шин и ветровых стекол, но также файлы схем, относящихся к рулю, креслам, окраске и т.д. В результате становится трудно управлять таким количеством модулей. Для решения данной проблемы можно включить дополнительный файл XSD c именем parts.xsd, который будет импортировать все модули, описывающие части автомобилей. Таким образом, любое изменение списка импортируемых схем будет затрагивать только parts.xsd, который был создан специально для управления модулями. При этом центральная схема будет выглядеть как в листинге 6.

Листинг 6. XML-схема, включающая один модуль parts.xsd вместо списка импортируемых документов
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      elementFormDefault="qualified" 
      targetNamespace="http://car.org/car" 
      xmlns:tr="http://car.org/tire" 
      xmlns:wnd="http://car.org/windscreen" 
      xmlns:car="http://car.org/car">
 <xs:include schemaLocation="parts.xsd"/>
 <xs:element name="car">
   <xs:complexType>
         <xs:sequence> 
               <xs:element ref="car:brand"/>
               <xs:element ref="car:type"/>
               <xs:element ref="car:kind"/> 
               <xs:element ref="tr:tires"/>
               <xs:element ref="wnd:windscreen"/>
         </xs:sequence>
   </xs:complexType>
 </xs:element>
 <xs:element name="brand" type="xs:NCName"/>
 <xs:element name="type" type="xs:NCName"/>
 <xs:element name="kind" type="xs:string"/>
</xs:schema>

Файл parts.xsd приведен в листинге 7.

Листинг 7. Файл parts.xsd, содержащий список импортируемых модулей
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      elementFormDefault="qualified" 
      targetNamespace="http://car.org/car" 
      xmlns:tr="http://car.org/tire" 
      xmlns:wnd="http://car.org/windscreen" 
      xmlns:car="http://car.org/car">
  <xs:import namespace="http://car.org/tire" schemaLocation="tr.xsd"/>
  <xs:import namespace="http://car.org/windscreen" schemaLocation="wnd.xsd"/>
</xs:schema>

Основным преимуществом использования parts.xsd является то, что полный перечень всех импортируемых XSD не приходится копировать во все схемы, в которых требуется описание частей автомобилей. Вместо этого достаточно просто включить parts.xsd.

Рискованные подходы к описанию схем на практике

В реальной схеме, когда число элементов (частей автомобилей) растет, приходится более тщательно относиться к описанию типов. В частности, использование локальных имен атрибутов в XML является практикой рискованной, хотя и весьма распространенной. В листинге 8 приведен фрагмент XML-документа с атрибутами, не принадлежащими явно определенному пространству имен.

Листинг 8. Пример документа XML, содержащего локальные имена атрибутов count
<tr:tires>
   <tr:tire count="4">
      <tr:brand>Michelin</tr:brand>
      <tr:type>Winter</tr:type>
   </tr:tire>
   <tr:tire count="1">
      <tr:brand>Michelin</tr:brand>
      <tr:type>Spare</tr:type>
   </tr:tire>
</tr:tires>
<wnd:windscreen count="1">
   <wnd:brand>Car glass</wnd:brand>
</wnd:windscreen>

Использование локальных имен атрибутов может привести к непредсказуемым результатам запросов на выборку элементов. Например, приведенный ниже запрос на языке XPath выбирает все элементы, содержащие атрибут count со значением 1. Однако при этом невозможно предвидеть, к какому пространству имен будут принадлежать результаты:

//[@count = 1]

На самом деле, в данном простейшем случае пространства имен предвидеть можно, но смысл в том, что это может стать источником проблем в более сложных ситуациях. Поэтому рекомендуется квалифицировать имена атрибутов, помещая их в определенные пространства имен. В листинге 9 показан слегка видоизмененный фрагмент XML.

Листинг 9. Пример документа XML, атрибуты которого принадлежат своим пространствам имен
[...]
   <tr:tires>
      <tr:tire tr:count="4">
            <tr:brand>
              Michelin
            </tr:brand>
            <tr:type>
              Winter
            </tr:type>
      </tr:tire>
      <tr:tire tr:count="1">
            <tr:brand>
              Michelin
            </tr:brand>
            <tr:type>
              Spare
            </tr:type>
      </tr:tire>
   </tr:tires>
   <wnd:windscreen wnd:count="1">
      <wnd:brand>
        Car glass
      </wnd:brand>
   </wnd:windscreen>
[...]

К счастью, подобная корректировка формата требует лишь незначительных изменений в XML-схеме (листинг 10).

Листинг 10. XML-схема для описания шин, определяющая пространства имен для атрибутов count
[...]
  <xs:element name="tire"> 
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="tr:brand"/>
        <xs:element ref="tr:type"/>
      </xs:sequence>
      <xs:attribute name="count" 
                    use="required" 
                    form="qualified" 
                    type="xs:integer"/>
    </xs:complexType>
  </xs:element>
[...]

Возможно, на данный момент улучшения не бросаются глаза, но тем не менее достаточно заметны. Своевременное предупреждение потенциальных конфликтов представляет собой полезную практику проектирования форматов XML.

Проектирование более общего формата

При использовании реляционных баз данных в качестве хранилища информации возникает соблазн установки однозначного соответствия между таблицами и полями в БД и конструкциями в XML. Это ограничивает не только свободу моделирования, но также распространяет ограничения базы данных на компоненты системы, непосредственно ее не использующие. Документ, приведенный в листинге 1, является характерным примером подобного прямого отображения реляционной модели данных в XML.

Вполне возможно, что на самом деле предпочтительной будет другая структура документа. Например, не исключено, что компоненты автомобиля должны быть перечислены начиная с передней части, чтобы упростить их автоматическую визуализацию. Подобный подход иллюстрируется в листинге 11, причем для отрисовки документ можно преобразовать в масштабируемый графический формат (Scalable Vector Graphics – SVG) при помощи XSLT. Это совсем не так сложно, как может показаться на первый взгляд. Сам документ показан в листинге 11.

Листинг 11. Пример альтернативной структуры XML-документа
<car>
   <brand>Volvo</brand>
   <type>C30</type>
   <kind>Small family car</kind>
   <tr:tire tr:count="2">
      <tr:brand>Michelin</tr:brand>
      <tr:type>Winter</tr:type>
   </tr:tire>
   <wnd:windscreen wnd:count="1">
      <wnd:brand>Car glass</wnd:brand>
   </wnd:windscreen>
   <tr:tire tr:count="2">
      <tr:brand>Michelin</tr:brand>
      <tr:type>Winter</tr:type>
   </tr:tire>
   <tr:tire tr:count="1">
      <tr:brand>Michelin</tr:brand>
      <tr:type>Spare</tr:type>
   </tr:tire>
</car>

Соответствующая версия главной XML-схемы показана в листинге 12. Обратите особое внимание на описание типа (complexType) элемента car.

Листинг 12. XML-схема, описывающая альтернативный формат для представления информации об автомобилях
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      elementFormDefault="qualified" 
      targetNamespace="http://car.org/car" 
      xmlns:tr="http://car.org/tire" 
      xmlns:wnd="http://car.org/windscreen" 
      xmlns:car="http://car.org/car">
  <xs:import namespace="http://car.org/tire" schemaLocation="tr.xsd"/>
  <xs:import namespace="http://car.org/windscreen" schemaLocation="wnd.xsd"/>
  <xs:element name="car">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="car:brand"/>
        <xs:element ref="car:type"/>
        <xs:element ref="car:kind"/>
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="tr:tire"/>
          <xs:element ref="wnd:windscreen"/>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="brand" type="xs:NCName"/>
  <xs:element name="type" type="xs:NCName"/>
  <xs:element name="kind" type="xs:string"/>
</xs:schema>

Сохранение и последующее точное восстановление данного файла из реляционной базы данных может представлять некоторые трудности. Для подобных целей иногда имеет смысл использовать NXD, например Exist DB – простую NXD с открытым кодом, либо IBM DB2 Express-C – также бесплатную реализацию, предоставляющую средства для интеграции с XML и реляционными СУБД. Кроме того, IBM DB2 Express-C поддерживает доступ к данным при помощи SQL или языков запросов, основанных на XML, например, XQuery.

Управление версиями и документирование XML-схем

В ряде случаев несколько организаций могут одновременно использовать несколько версий одной XML-схемы. Это вполне нормально при условии, что каждая организация знает свой номер версии и чем она отличается от предыдущей. Для этого следует использовать аннотации – специальные XSD-элементы, содержащие версию схемы и информацию об остальных элементах. Аннотации могут содержаться в двух элементах: documentation и appinfo.

Имя documentation говорит само за себя. Старайтесь документировать каждый элемент в вашей схеме, и вы не пожалеете о потраченном времени. Элемент appinfo представляет больший интерес, так как на его содержимое не накладывается никаких ограничений. В частности, вы можете определить собственный дочерний элемент version, в котором будут храниться версии других элементов. При этом XML-схема будет выглядеть подобно показанной в листинге 13.

Листинг 13. Пример XML-схемы, содержащей специализированные аннотации
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      elementFormDefault="qualified" 
      targetNamespace="http://car.org/tire" 
      xmlns:tr="http://car.org/tire" 
      xmlns:custom="http://car.org/custom">
 <xs:element name="tires">
   <xs:annotation>
         <xs:appinfo>
                <custom:version>0.91</custom:version>
             </xs:appinfo>
             <xs:documentation>
                Describes a set of tires.
             </xs:documentation>
   </xs:annotation>
   <xs:complexType>

Подобные элементы и атрибуты, хранящие информацию о версиях, могут значительно упростить управление схемами, использующимися большой группой организаций, даже несмотря на то, что они непосредственно не поддерживаются ни одним XSD-процессором. Как известно, с XML-схемами, как и с документами XML работают не только компьютеры, но и живые люди, поэтому информация о номере версии никогда не помешает.

Немного о расширениях

Последнее, о чем стоит упомянуть при рассмотрении XML-схем – это элемент extension. В предыдущем разделе был приведен пример расширяемости элемента appinfo, который может иметь произвольное содержимое. Элемент extension позволяет накладывать ограничения на расширение типов. В листинге 14 приведен пример схемы, в которой описывается расширение типа basicTire для включения элемента size.

Листинг 14. XML-схема для описания шин, расширяющая тип basicTire для включения элемента size
<xs:element name="tire" type="tr:sizedTire"/>
  <xs:complexType name="basicTire">
    <xs:sequence>
      <xs:element ref="tr:brand"/>
      <xs:element ref="tr:type"/>
    </xs:sequence>
    <xs:attribute name="count" use="required" form="qualified" type="xs:integer"/>
  </xs:complexType>
  <xs:complexType name="sizedTire">
    <xs:complexContent>
      <xs:extension base="tr:basicTire">
        <xs:sequence>
          <xs:element ref="tr:size"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

Кроме того, язык XML-схем позволяет при необходимости запрещать расширения определенных элементов, в частности, basicType. Более подробную информацию можно почерпнуть из спецификации XSD, ссылка на которую приведена в разделе Ресурсы. Ознакомившись с ней, вы увидите, что XML-схемы обладают гораздо более широким кругом возможностей, чем можно охватить в одной статье.

Заключение

Как видите, если вы хотите избежать проблем с сопровождением форматов XML в крупных организациях, то следует уделять XML-схемам несколько больше внимания, чем просто техническим деталям, которые генерируются автоматически. Существует гораздо больше способов улучшения сопровождаемости форматов XML, чем было показано в этой статье. В частности, структуры XML можно описывать на других языках, таких как Schematron и RELAX NG. Вне зависимости от того, какой из них вы выберете, всегда следует проектировать формат XML таким образом, чтобы он учитывал интересы всех сторон, участвующих в обмене информацией.

Ресурсы


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

Copyright 1994-2016 "-"