воскресенье, 6 января 2013 г.

Паттерны проектирования XML-схем

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

Существует несколько паттернов проектирования XML-схем. Использование данных паттернов позволяет описать одну и ту же структуру XML-документа разными способами, каждый из которых имеет свои преимущества и недостатки. Рассмотрим данные способы подробнее.


Матрешка (Russian Doll)


Суть данного паттерна заключается в том, что XML Schema является зеркалом описываемого ею XML-документа: если элемент A, содержит элемент B, а тот в свою очередь - элемент C, то и в XML Schema описания данных элементов будут вложены друг в друга.

Пример:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"

           xmlns:tns="http://www.rt.ru/integration/messages/crm/abonent"

           targetNamespace="http://www.rt.ru/integration/messages/crm/abonent"

           elementFormDefault="qualified">

  <xsd:element name="AbonentMessage">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element name="Header">

          <xsd:complexType>

            <xsd:sequence>

              <xsd:element name="RecordId" type="xsd:decimal"/>

              <xsd:element name="Operation">

                <xsd:simpleType>

                  <xsd:restriction base="xsd:string">

                    <xsd:enumeration value="A"/>

                    <xsd:enumeration value="U"/>

                    <xsd:enumeration value="D"/>

                  </xsd:restriction>

                </xsd:simpleType>

              </xsd:element>

              <xsd:element name="OperatorId" type="xsd:decimal"/>

              <xsd:element name="TaskId" type="xsd:decimal"/>

            </xsd:sequence>

          </xsd:complexType>

        </xsd:element>

        <xsd:element name="Body">

          <xsd:complexType>

            <xsd:sequence>

              <xsd:element name="AbonentType" minOccurs="1" maxOccurs="1">

                <xsd:simpleType>

                  <xsd:restriction base="xsd:string">

                    <xsd:enumeration value="Organization"/>

                  <xsd:enumeration value="Person"/>

                  </xsd:restriction>

                </xsd:simpleType>

              </xsd:element>            

              <xsd:element name="Address">

                <xsd:complexType>

                  <xsd:sequence>      

                    <xsd:element name="AddressId" type="xsd:integer"/>

                    <xsd:element name="Country" type="xsd:string"/>        

                    <xsd:element name="Region" type="xsd:string"/>

                    <xsd:element name="District" type="xsd:string"/>

                    <xsd:element name="City" type="xsd:string"/>

                    <xsd:element name="Street" type="xsd:string"/>

                    <xsd:element name="House" type="xsd:string"/>                

                    <xsd:element name="Flat" type="xsd:string"/>

                    <xsd:element name="PhoneNumber" type="xsd:string"/>

                  </xsd:sequence>

                </xsd:complexType>

              </xsd:element>

              <xsd:element name="LegalAddress">

                <xsd:complexType>

                  <xsd:sequence>      

                    <xsd:element name="AddressId" type="xsd:integer"/>

                    <xsd:element name="Country" type="xsd:string"/>        

                    <xsd:element name="Region" type="xsd:string"/>

                    <xsd:element name="District" type="xsd:string"/>

                    <xsd:element name="City" type="xsd:string"/>

                    <xsd:element name="Street" type="xsd:string"/>

                    <xsd:element name="House" type="xsd:string"/>                

                    <xsd:element name="Flat" type="xsd:string"/>

                    <xsd:element name="PhoneNumber" type="xsd:string"/>

                  </xsd:sequence>

                </xsd:complexType>

              </xsd:element>

              <xsd:element name="INN" type="xsd:string"/>

              <xsd:element name="Name" type="xsd:string"/>  

              <xsd:element name="FullName" type="xsd:string"/>

            </xsd:sequence>            

          </xsd:complexType>

        </xsd:element>

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

 

Достоинства паттерна: имеется только один корневой элемент, что обеспечивает максимально возможную инкапсуляцию.

Недостатки паттерна: невозможно повторное использование элементов (в приведенном примере видно, что элементы Address и LegalAddress имеют по-сути один и тот же тип, но их описание дублируется).

Ломтик салями (Salami Slice)


Суть данного паттерна заключается в том, что описываемый XML-документ разделяется на составные элементы, каждый из которых описывается в XML Schema как глобальный. Затем описанные элементы соединяются воедино. Данный паттерн находится на противоположном конце спектра по отношению к паттерну "Матрешка".

Пример:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"

           xmlns:tns="http://www.rt.ru/integration/messages/crm/abonent"

           targetNamespace="http://www.rt.ru/integration/messages/crm/abonent"

           elementFormDefault="qualified">



  <xsd:element name="RecordId" type="xsd:decimal"/>



  <xsd:element name="Operation">

    <xsd:simpleType>

      <xsd:restriction base="xsd:string">

        <xsd:enumeration value="A"/>

        <xsd:enumeration value="U"/>

        <xsd:enumeration value="D"/>

      </xsd:restriction>

    </xsd:simpleType>

  </xsd:element>



  <xsd:element name="OperatorId" type="xsd:decimal"/>



  <xsd:element name="TaskId" type="xsd:decimal"/>



  <xsd:element name="Header">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element ref="tns:RecordId"/>

        <xsd:element ref="tns:Operation"/>

        <xsd:element ref="OperatorId"/>

        <xsd:element ref="TaskId"/>

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>



  <xsd:element name="AbonentType">

    <xsd:simpleType>

      <xsd:restriction base="xsd:string">

        <xsd:enumeration value="Organization"/>

        <xsd:enumeration value="Person"/>

      </xsd:restriction>

    </xsd:simpleType>

  </xsd:element>            



  <xsd:element name="AddressId" type="xsd:integer"/>



  <xsd:element name="Country" type="xsd:string"/>        

 

  <xsd:element name="Region" type="xsd:string"/>

 

  <xsd:element name="District" type="xsd:string"/>

 

  <xsd:element name="City" type="xsd:string"/>

 

  <xsd:element name="Street" type="xsd:string"/>

 

  <xsd:element name="House" type="xsd:string"/>                

 

  <xsd:element name="Flat" type="xsd:string"/>

                   

  <xsd:element name="PhoneNumber" type="xsd:string"/>



  <xsd:element name="Address">

    <xsd:complexType>

      <xsd:sequence>      

        <xsd:element ref="tns:AddressId"/>

        <xsd:element ref="tns:Country"/>        

        <xsd:element ref="tns:Region"/>

        <xsd:element ref="tns:District"/>

        <xsd:element ref="tns:City"/>

        <xsd:element ref="tns:Street"/>

        <xsd:element ref="tns:House"/>                

        <xsd:element ref="tns:Flat"/>

        <xsd:element ref="tns:PhoneNumber"/>

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

 

  <xsd:element name="LegalAddress">

    <xsd:complexType>

      <xsd:sequence>      

        <xsd:element ref="tns:AddressId"/>

        <xsd:element ref="tns:Country"/>        

        <xsd:element ref="tns:Region"/>

        <xsd:element ref="tns:District"/>

        <xsd:element ref="tns:City"/>

        <xsd:element ref="tns:Street"/>

        <xsd:element ref="tns:House"/>

        <xsd:element ref="tns:Flat"/>

        <xsd:element ref="tns:PhoneNumber"/>

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>



  <xsd:element name="INN" type="xsd:string"/>

 

  <xsd:element name="Name" type="xsd:string"/>  

 

  <xsd:element name="FullName" type="xsd:string"/>

 

  <xsd:element name="Body">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element ref="tns:AbonentType" minOccurs="1" maxOccurs="1"/>

        <xsd:element ref="tns:Address"/>

        <xsd:element ref="tns:LegalAddress"/>

        <xsd:element ref="tns:INN"/>

        <xsd:element ref="tns:Name"/>  

        <xsd:element ref="tns:FullName"/>

      </xsd:sequence>            

    </xsd:complexType>

  </xsd:element>



  <xsd:element name="AbonentMessage">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element ref="tns:Header"/>      

        <xsd:element ref="tns:Body"/>

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

 

Достоинства паттерна: схема содержит несколько глобальных элементов, что позволяет при необходимости легко разделить ее на отдельные файлы. Собственно, каждый элемент схемы является глобальным, что позволяет использовать данные элементы в других схемах.

Недостатки паттерна: не поддерживается инкапсуляция и сокрытие внутреннего представления элементов схемы.

Подъемные жалюзи (Venetian Blind)


Суть данного паттерна заключается в том, что описываемый XML-документ разделяется на составные типы, каждый из которых описывается в XML Schema как глобальный. Затем объявляется корневой элемент, соответствующий глобальному типу, соединяющему схему воедино.

Пример:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"

           xmlns:tns="http://www.rt.ru/integration/messages/crm/abonent"

           targetNamespace="http://www.rt.ru/integration/messages/crm/abonent"

           elementFormDefault="qualified">



  <xsd:simpleType name="tOperation">

    <xsd:restriction base="xsd:string">

      <xsd:enumeration value="A"/>

      <xsd:enumeration value="U"/>

      <xsd:enumeration value="D"/>

    </xsd:restriction>

  </xsd:simpleType>



  <xsd:complexType name="tHeader">

    <xsd:sequence>

      <xsd:element name="RecordId" type="xsd:decimal"/>

      <xsd:element name="Operation" type="tns:tOperation"/>

      <xsd:element name="OperatorId" type="xsd:decimal"/>

      <xsd:element name="TaskId" type="xsd:decimal"/>

    </xsd:sequence>

  </xsd:complexType>



  <xsd:simpleType name="tAbonentType">

    <xsd:restriction base="xsd:string">

      <xsd:enumeration value="Organization"/>

      <xsd:enumeration value="Person"/>

    </xsd:restriction>

  </xsd:simpleType>



  <xsd:complexType name="tAddress">

    <xsd:sequence>      

      <xsd:element name="AddressId" type="xsd:integer"/>

      <xsd:element name="Country" type="xsd:string"/>        

      <xsd:element name="Region" type="xsd:string"/>

      <xsd:element name="District" type="xsd:string"/>

      <xsd:element name="City" type="xsd:string"/>

      <xsd:element name="Street" type="xsd:string"/>

      <xsd:element name="House" type="xsd:string"/>                

      <xsd:element name="Flat" type="xsd:string"/>

      <xsd:element name="PhoneNumber" type="xsd:string"/>

    </xsd:sequence>

  </xsd:complexType>



  <xsd:complexType name="tBody">

    <xsd:sequence>

      <xsd:element name="AbonentType" type="tns:tAbonentType" minOccurs="1" maxOccurs="1"/>

      <xsd:element name="Address" type="tns:tAddress"/>

      <xsd:element name="LegalAddress" type="tns:tAddress"/>

      <xsd:element name="INN" type="xsd:string"/>

      <xsd:element name="Name" type="xsd:string"/>  

      <xsd:element name="FullName" type="xsd:string"/>

    </xsd:sequence>            

  </xsd:complexType>



  <xsd:complexType name="tAbonentMessage">

    <xsd:sequence>

      <xsd:element name="Header" type="tns:tHeader"/>

      <xsd:element name="Body" type="tns:tBody"/>

    </xsd:sequence>

  </xsd:complexType>



  <xsd:element name="AbonentMessage" type="tns:tAbonentMessage"/>

</xsd:schema>

 

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

Недостатки паттерна: повсеместное применение пространств имен, что затрудняет использование схемы.

Какой же паттерн использовать


  • Если требуется гибкость при управлении пространствами имен, а так же необходимо повторное использование компонентов схемы, то следует использовать паттерн Подъемные жалюзи.

  • Если необходимо обеспечить авторам XML-документов возможность подстановки элементов, то следует использовать паттерн Ломтик салями.

  • Если необходимо минимизировать размер схемы и связанность (coupling) между ее компонентами, то следует использовать паттерн Матрешка.

Ресурсы



Понравилось сообщение - подпишитесь на блог и Twitter

2 комментария:

OceanReBorn комментирует...

вот никогда не понимал "фишку" в этих бесконечных <xsd: в файле схемы, почему не начать файл с "<schema xmlns="http://www.w3.org/2001/XMLSchema" ... " и таким образом сделать пустым префикс для тегов относящихся к xml schema?
И "xmlns:tns="http://www.rt.ru/integration/messages/crm/abonent"" tns - подразумевается targetNamespace? тогда это тоже не удачный выбор, по-моему было бы лучше и в какой-то степени умнее с "xmlns:abonent="http://www.rt.ru/integration/messages/crm/abonent""

Pavel Samolisov комментирует...

Как-то странно, xsd вас почему-то напрягает, а префикс из 7-ми букв вместо префикса из 3-х букв почему-то нет. Непоследовательность какая-то.

Все ваши рассуждения можно считать актуальными, если в файле схемы используется только одно пространство имен. Если же в схему импортируется еще 4 - 5 схем, каждая из которых имеет свое пространство имен (а при проектировании сколь нибудь сложной канонической модели данных это - не редкость), то префиксы xsd и tns облегчают чтение, сразу становится понятно, что вот это - xsd - из пространства имен самой XML Schema, а вот это - tns - из той схемы, которую мы описываем, а всякие abn, cust, org и т.д. - мы импортировали.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!