Данная статья не является исчерпывающей документацией и написана в первую очередь для себя, чтобы всегда можно было вернуться и вспомнить то, что забыл. Здесь упомянуты все самые важные, по моему мнению, фичи инструмента, а также то, что может быть полезно и актуально в моей работе. Поэтому статья будет полезна начинающим айтишникам и тем, кому нужно освежить свои знания.
Что это?
Самое главное, что нужно понимать: XSD (XML Schema Definition) — это схема, то есть описание данных взаимодействия. Схемы бывают разные, например, наша XSD, которую обычно используют для описания структур в формате XML, или JSON-schema, — предназначена для описания структур в формате JSON. Использование схем в контрактах API сейчас уже является стандартом де-факто, и вот почему:
Переиспользование. Удобно один раз описать формат и далее везде этим пользоваться, или «переиспользовать» для описания новых форматов на основе существующего.
Валидация, парсинг и рендеринг (десериализация и сериализация). Когда интегрируют две системы (сервиса), формат обычно описывают в одной и передают во вторую в виде схемы. В итоге получается единый формат в обеих системах, и не будет ситуации, когда кто-то отправит сообщение, которое вторая система не сможет провалидировать или распарсить. Важно только своевременно обновлять схему у отправителя и получателя при её изменении, либо внедрить версионирование.
Систематизация и порядок. Когда все контракты описаны в схемах (я считаю, что лучше всего одна схема = один микросервис), возникает порядок, ведь внутри алгоритмов в коде все данные берутся не из воздуха. Благодаря этому также экономится время на разработку, тестирование, и даже иногда поддержку. Тот же аналитик может скачать проект и посмотреть существующие схемы, что даст ему много информации, особенно если схемы тщательно ведутся разработчиками или теми, кто их проектирует.
Одним словом — удобство, вот что даёт использование схемы.
Возвращаясь к 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>
Типы
С ними чуть-чуть сложнее, так как они бывают трёх видов:
Элементарные — используются внутри
element
с помощью атрибутаtype
и представляют из себя стандартные типы данных вродеstring
,boolean
и т. д.Кастомные — используются, когда помимо элементарного типа требуется наложение дополнительных ограничений. Чаще всего применяют для создания перечисления возможных значений (enumeration) или наложения на поле (
element
) какого-нибудь паттерна. Используется с помощью тегаsimpleType
.Сложные — используются в первую очередь для описания сложных структур, таких как объекты с вложенными элементами. Используется с помощью тега 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
.
Индикаторы порядка
Одно из основных понятий в 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
добавляет базовые поля личности: ФИО, пол, возраст.
Из интересного в примере:
Поле
inn
имеет кастомный тип, который «базируется» на типеstring
, но имеет определённый паттерн, описанный с помощью регулярного выражения. Таким образом, мы получили обязательное поле, которое будет валидно только в том случае, если его заполнить 12-ю или 10-ю цифрами.В двух местах схемы я использовал
Enum
'ы. Суть примерно та же, что и у элементаinn
, поле также базируется на типеstring
, однако вместо паттерна используется ограничение в виде возможных вариантов заполнения. Например, полеtype
элементаcontact
может быть заполнено исключительно тремя значениями:EMAIL
,PHONE
илиTELEGRAM
.
Заключение
Принято считать, что XML медленно, но верно умирает (соответственно, и XSD тоже). И всё же в моём сердце они занимают отдельное место. Для меня XSD вместе с XML — понятная и наглядная структура, которая имеет кучу возможностей. А умирает, потому что на замену SOAP пришёл модный и молодёжный простой и тоже решающий почти все проблемы REST поверх HTTP. С его появлением и почти повсеместным использованием количество XML начало сокращаться в пользу JSON.
Кстати, у меня есть Telegram-канал «Айтишник обыкновенный», где я делюсь опытом и знаниями из IT-индустрии, а также выкладываю мемы. Лучшая поддержка — подписка на мой канал ❤️
А ещё рекомендую глянуть мои статьи про диаграммы последовательности и BPMN. Я уверен, что каждый найдёт для себя в них что-то новое и полезное. Пишите в комментариях, о чём забыл рассказать, но точно стоило бы в рамках статьи.
Всем добра!
mayorovp
Если вам нужен JSON, то и использовать следует JSON. Не надо ограничивать XML подмножеством, совместимым с JSON - это лишь объединяет их недостатки.
Нет, эта штука ожидает прежде всего один из описанных схемой элементов.
default_itshnik Автор
Возможно, посыл про JSON был не особо понятен, но я скорее подсветил, что его можно сделать на основе XSD, но заниматься, конечно, этим не стоит)
По поводу strict – в точку, что-то у меня глаз замылился, поправил.
Спасибо❤️
CrazyElephant_X
Да, вроде понятно про JSON, хотя возможно что я так же думаю из-за этого мне понятно)
apidev
Не открытый: не нужно. Не нужно забивать гвозди микроскопом, не нужно валидировать JSON XSD'шкой. Для JSON есть JSON Schema. Никогда не забывайте про:
Я бы добавил: или пользоваться. Есть у меня в компании одни садисты, которые в продакшн вечно х-х, а их продукт всем наш ИБ навязал.
Пожалуйста, придерживайтесь инструментов, которые не будут заставлять пользователей постоянно страдать.