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

Что это?

Самое главное, что нужно понимать: XSD (XML Schema Definition) — это схема, то есть описание данных взаимодействия. Схемы бывают разные, например, наша XSD, которую обычно используют для описания структур в формате XML, или JSON-schema, — предназначена для описания структур в формате JSON. Использование схем в контрактах API сейчас уже является стандартом де-факто, и вот почему:

  1. Переиспользование. Удобно один раз описать формат и далее везде этим пользоваться, или «переиспользовать» для описания новых форматов на основе существующего.

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

  3. Систематизация и порядок. Когда все контракты описаны в схемах (я считаю, что лучше всего одна схема = один микросервис), возникает порядок, ведь внутри алгоритмов в коде все данные берутся не из воздуха. Благодаря этому также экономится время на разработку, тестирование, и даже иногда поддержку. Тот же аналитик может скачать проект и посмотреть существующие схемы, что даст ему много информации, особенно если схемы тщательно ведутся разработчиками или теми, кто их проектирует.

Одним словом — удобство, вот что даёт использование схемы.

Возвращаясь к XSD, с её помощью можно описать как простейшие типы (string, decimal), так и производные из них (например, string с паттерном регулярки), комплексные типы (объекты/списки), порядок, атрибуты и обязательность полей.

Регулярки, они же регулярные выражения (Regular expression, RegExp) — это

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

Из чего состоит XSD?

Основные объекты, из которых состоит XSD-схема:

  • элементы (зелёный цвет);

  • типы (синий);

  • индикаторы порядка (красный).

Основные объекты
Основные объекты

Обычно структуру строят таким образом, что есть корневой элемент (как на картинке outer) и вложенные элементы (как на картинке inner). Когда элемент сложный, то есть состоит из нескольких других элементов (имеет вложенность), то его считают комплексным (от complexType). Сложный элемент всегда содержит внутри тег complexType, индикатор порядка (как на картинке sequence) и сами вложенные элементы. Подробнее обо всём этом дальше.

Элементы

element — это базовая единица в схеме XSD, которая и представляет собой само поле. Он содержит название (атрибут name) и тип (об этом дальше). Ещё element по умолчанию содержит ограничители частотности minOccurs и maxOccurs, равные единице, даже если их не указывать в виде атрибутов внутри элемента. minOccurs отвечает за минимальное количество повторений элемента, а maxOccurs — за максимальное. Что это означает?

  • Поля без этих атрибутов являются обязательными.

  • Поля с minOccurs=0 являются необязательными.

  • Поля  с maxOccurs="unbounded" являются массивом с неограниченным количеством элементов, но не менее одного. Можно ещё указать вместо unbounded, например, число 5, тогда у поля будет ограничение в виде 1-5 повторений.

  • Поля с заполненными minOccurs=0 и maxOccurs="unbounded" являются необязательным массивом с неограниченным количеством элементов. Здесь также можно указать вместо unbounded число для явного ограничения повторений.

Конечно же, для поля ещё можно задать описание. Делается это с помощью тега annotation и выглядит в схеме так:

<xs:element name="example" type="string">
  <xs:annotation>
    <xs:documentation>Текст с описанием элемента</xs:documentation>
  </xs:annotation>
</xs:element>

Типы

С ними чуть-чуть сложнее, так как они бывают трёх видов:

  1. Элементарные — используются внутри element с помощью атрибута type и представляют из себя стандартные типы данных вроде string, boolean и т. д.

  2. Кастомные — используются, когда помимо элементарного типа требуется наложение дополнительных ограничений. Чаще всего применяют для создания перечисления возможных значений (enumeration) или наложения на поле (element) какого-нибудь паттерна. Используется с помощью тега simpleType.

  3. Сложные — используются в первую очередь для описания сложных структур, таких как объекты с вложенными элементами. Используется с помощью тега complexType.

Enumeration, или Enum, — это

отдельный специальный тип, который довольно часто используют для перечисления возможных значений. Например, можно задать поле с типом клиента (ФЛ, ЮЛ или ИП). Под такое поле можно определить список возможных значений, например: legal, physical и solopreneur. Подробнее можно будет посмотреть дальше в примере.

Типы можно создавать как внутри элемента, так и отдельно извне (причём даже в отдельном файле), чтобы использовать их повторно для разных элементов, а также расширять ими друг друга. Типы, которые объявлены отдельно от элемента, содержат название (атрибут name), с помощью которого этот тип и будет присваиваться элементам или типам для расширения.

Стандартные типы данных, которые поддерживаются в XSD:

  • string;

  • decimal;

  • integer;

  • boolean;

  • date;

  • time.

Поскольку XSD в первую очередь предназначен для описания XML, то с помощью типов можно также задать для элемента атрибуты. Делается это, например, так:

<xs:element name="product">
  <xs:complexType>
    <xs:simpleContent>
      <xs:restriction base="xs:string">
        <xs:attribute name="coast" type="xs:integer" use="required"/>
        <xs:attribute name="sale" type="xs:boolean"/>
        <xs:attribute name="size" type="xs:integer"/>
        <xs:attribute name="model" type="xs:string"/>
      </xs:restriction>
    </xs:simpleContent>
  </xs:complexType>
</xs:element>

А вот так это будет выглядеть в XML-примере:

<product coast="12000" sale="false" size="24" model="Asus">Монитор</product>

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

P.S. Да-да, не смотря на то, что XSD используется в первую очередь для описания структуры сообщения или запроса в формате XML, на самом деле на его основе можно также формировать и JSON. По сути указываешь программе структуру документа — XSD, а далее говоришь, в какой формат эту структуру преобразовать — например, в XML или JSON. То есть возможно ли описать с помощью XSD сообщение в JSON? Да. Нужно ли так делать? Вопрос открытый, так как для проектирования контрактов в формате JSON есть специальные для этого инструменты.

Any

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

<xs:complexType name="requestType">
  <xs:sequence>
    <xs:element name="requestId" type="xs:string" />
    <xs:element name="requestDateTime" type="xs:string" />
    <xs:element name="sender" type="xs:string" />
    <xs:element name="receiver" type="xs:string" />
    <xs:element name="body" type="xs:anyType" />
  </xs:sequence>
</xs:complexType>

Вот такое сообщение может быть сформировано на основе этой схемы:

<request>
  <requestId>6e8ae52d-d143-47d3-937b-765005b86b4a</requestId>
  <requestDateTime>1970-01-01T00:00:00</requestDateTime>
  <sender>web-service</sender>
  <receiver>library-service</receiver>
  <body>
    <title>XML Schema</title>
    <description>If you need to create or use formal descriptions of XML vocabularies, the W3C's XML Schema offers a powerful set of tools...</description>
    <author>Eric van der Vlist</author>
  </body>
</request>

Помимо типа anyType в XSD существует элемент any. Кроме того, что теперь поле может называться не только body, есть ещё одна особенность: any имеет дополнительную настройку (processContents), которая позволяет уточнить то, как мы хотим его использовать. Возможные значения этой настройки:

  • skip — полностью пропускает валидацию.

  • strict — ожидает, что на месте any будет один из описанных по схеме элементов. Если такой элемент не будет найден, то валидация не будет пройдена.

  • lax — ожидает, что на месте any будет один из описанных по схеме элементов. Если такой элемент не будет найден, то валидация будет пропущена.

Пример выше можно переписать с заменой anyType на any, добавив при этом валидацию по элементу с помощью processContents="strict":

<xs:element name="request" type="requestType"/>
<xs:element name="book" type="bookType"/>
<xs:element name="notification" type="xs:string"/>

<xs:complexType name="bookType">
  <xs:sequence>
    <xs:element name="title" type="xs:string" />
    <xs:element name="description" type="xs:string" />
    <xs:element name="author" type="xs:string" />
  </xs:sequence>
</xs:complexType>

<xs:complexType name="requestType">
  <xs:sequence>
    <xs:element name="requestId" type="xs:string" />
    <xs:element name="requestDateTime" type="xs:string" />
    <xs:element name="sender" type="xs:string" />
    <xs:element name="receiver" type="xs:string" />
    <xs:any processContents="strict" />
  </xs:sequence>
</xs:complexType>

Теперь сообщение будет выглядеть так:

<request>
  <requestId>6e8ae52d-d143-47d3-937b-765005b86b4a</requestId>
  <requestDateTime>1970-01-01T00:00:00</requestDateTime>
  <sender>web-service</sender>
  <receiver>library-service</receiver>
  <book>
    <title>XML Schema</title>
    <description>If you need to create or use formal descriptions of XML vocabularies, the W3C's XML Schema offers a powerful set of tools...</description>
    <author>Eric van der Vlist</author>
  </book>
</request>

Вариант с полем notification вместо book также будет обработан корректно. Однако, если положить какое-то другое поле, то валидация не будет пройдена. Для таких случаев подойдёт настройка lax или skip

Типы Any
Типы Any

Индикаторы порядка

Одно из основных понятий в XSD — индикаторы порядка. Чаще всего используются индикаторы sequence и all. sequence необходим в тех случаях, когда требуется чётко определённый порядок полей, то есть последовательность, описанная в схеме, является обязательной. Индикатор all, наоборот, говорит о том, что порядок полей можно не соблюдать. Немного забегая вперёд, можете посмотреть для наглядности в примере XML на элементы внутри user: они как объявлены в XSD, так и передаются. Далее взгляните на массив contactList, который используется с индикатором all: видно, как чередуются поля type и value,  это абсолютно корректно.

Помимо этих двух индикаторов порядка ещё есть choice, который тоже очень удобен и используется как оператор «исключающее ИЛИ», но гораздо реже. То есть, если вместо, например, all использовать choice, то XML будет формироваться с одним из элементов на выбор. Индикатор choice удобнее всего использовать, когда одна XSD используется для описания всех API микросервиса. Например:

<xs:choice>
  <xs:element name="GetClientDataByIdRequest" type="GetClientDataByIdRequestType" />
  <xs:element name="GetClientDataByIdResponse" type="GetClientDataByIdResponseType" />
  <xs:element name="SendNotificationRequest" type="SendNotificationRequestType" />
  <xs:element name="SendNotificationResponse" type="SendNotificationResponseType" />
  <xs:element name="CheckClientRequest" type="CheckClientRequestType" />
  <xs:element name="CheckClientResponse" type="CheckClientResponseType" />
</xs:choice>

Пример

Вот небольшой пример XSD-схемы, в котором я постарался адекватно задействовать почти все описанные выше фичи:

XSD пример
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="user">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="personType">
          <xs:sequence>
            <xs:element name="inn">
             <xs:simpleType>
              <xs:restriction base="xs:string">
               <xs:pattern value="\d{12}|\d{10}"/>
              </xs:restriction>
             </xs:simpleType>
            </xs:element>
            <xs:element name="contactList" type="contactListType" />
          </xs:sequence>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:complexType name="personType">
    <xs:sequence>
      <xs:element name="firstname" type="xs:string" />
      <xs:element name="secondname" type="xs:string" />
      <xs:element name="patronymic" type="xs:string" minOccurs="0" />
      <xs:element name="gender" minOccurs="0">
        <xs:simpleType final="restriction" >
          <xs:restriction base="xs:string">
              <xs:enumeration value="MALE" />
              <xs:enumeration value="FEMALE" />
          </xs:restriction>
        </xs:simpleType>
      </xs:element>
      <xs:element name="age" type="xs:integer" />
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="contactListType">
    <xs:sequence>
      <xs:element maxOccurs="unbounded" name="contact">
        <xs:complexType>
          <xs:all>
            <xs:element name="type">
              <xs:simpleType final="restriction" >
                <xs:restriction base="xs:string">
                    <xs:enumeration value="EMAIL" />
                    <xs:enumeration value="PHONE" />
                    <xs:enumeration value="TELEGRAM" />
                </xs:restriction>
              </xs:simpleType>
            </xs:element>
            <xs:element name="value" type="xs:string" />
          </xs:all>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Вот пример XML, который будет сформирован на основе вышеприведённой схемы:

<user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <firstname>Иван</firstname>
  <secondname>Иванов</secondname>
  <patronymic>Иванович</patronymic>
  <age>30</age>
  <inn>1234567890</inn>
  <contactList>
    <contact>
      <value>8(909)999-99-99</value>
      <type>PHONE</type>
    </contact>
    <contact>
      <type>TELEGRAM</type>
      <value>default_itshnik</value>
    </contact>
  </contactList>
</user>

Итак, вышеприведённая схема состоит из трёх основных объектов: элемента user и двух типов personType и contactListType. user здесь корневой и основной элемент XSD, который расширяется типом personType. Сам user содержит в себе только два элемента: inn и contactList, а расширение типом personType добавляет базовые поля личности: ФИО, пол, возраст.

Из интересного в примере:

  1. Поле inn имеет кастомный тип, который «базируется» на типе string, но имеет определённый паттерн, описанный с помощью регулярного выражения. Таким образом, мы получили обязательное поле, которое будет валидно только в том случае, если его заполнить 12-ю или 10-ю цифрами.

  2. В двух местах схемы я использовал Enum'ы. Суть примерно та же, что и у элемента inn, поле также базируется на типе string, однако вместо паттерна используется ограничение в виде возможных вариантов заполнения. Например, поле type элемента contact может быть заполнено исключительно тремя значениями: EMAIL, PHONE или TELEGRAM.

Заключение

Принято считать, что XML медленно, но верно умирает (соответственно, и XSD тоже). И всё же в моём сердце они занимают отдельное место. Для меня XSD вместе с XML — понятная и наглядная структура, которая имеет кучу возможностей. А умирает, потому что на замену SOAP пришёл модный и молодёжный простой и тоже решающий почти все проблемы REST поверх HTTP. С его появлением и почти повсеместным использованием количество XML начало сокращаться в пользу JSON.

Кстати, у меня есть Telegram-канал «Айтишник обыкновенный», где я делюсь опытом и знаниями из IT-индустрии, а также выкладываю мемы. Лучшая поддержка — подписка на мой канал ❤️

А ещё рекомендую глянуть мои статьи про диаграммы последовательности и BPMN. Я уверен, что каждый найдёт для себя в них что-то новое и полезное. Пишите в комментариях, о чём забыл рассказать, но точно стоило бы в рамках статьи.

Всем добра!

Комментарии (7)


  1. mayorovp
    28.01.2025 06:03

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

    Если вам нужен JSON, то и использовать следует JSON. Не надо ограничивать XML подмножеством, совместимым с JSON - это лишь объединяет их недостатки.

    strict — ожидает, что на месте элемента any будет элемент с одним из описанных по схеме типов.

    Нет, эта штука ожидает прежде всего один из описанных схемой элементов.


    1. default_itshnik Автор
      28.01.2025 06:03

      Возможно, посыл про JSON был не особо понятен, но я скорее подсветил, что его можно сделать на основе XSD, но заниматься, конечно, этим не стоит)

      По поводу strict – в точку, что-то у меня глаз замылился, поправил.

      Спасибо❤️


      1. CrazyElephant_X
        28.01.2025 06:03

        Да, вроде понятно про JSON, хотя возможно что я так же думаю из-за этого мне понятно)


      1. apidev
        28.01.2025 06:03

        То есть возможно ли описать с помощью XSD сообщение в JSON? Да. Нужно ли так делать? Вопрос открытый, так как для проектирования контрактов в формате JSON есть специальные для этого инструменты.

        Не открытый: не нужно. Не нужно забивать гвозди микроскопом, не нужно валидировать JSON XSD'шкой. Для JSON есть JSON Schema. Никогда не забывайте про:

        Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте

        Я бы добавил: или пользоваться. Есть у меня в компании одни садисты, которые в продакшн вечно х-х, а их продукт всем наш ИБ навязал.

        Пожалуйста, придерживайтесь инструментов, которые не будут заставлять пользователей постоянно страдать.