Архитектура: искусство делать излишнее необходимым.
Фредерик Кислер
Ни для кого давно уже не секрет, что для любого web-сервиса на протоколе SOAP с сообщениями в формате XML верным и проверенным временем решением является предварительная разработка XML Schema (xsd-схемы), описывающей типы данных и структуру XML сообщений. При этом подходе у разработчиков существует явное преимущество: у них есть строгие стандартизированные правила по структуре сообщений, которые заданы в схеме, число правил конечно, и они позволяют автоматизировать проверку любого нового сообщения в формате XML.
Но также известно, что язык XML потеснился языком разметки JSON (JavaScript Object Notation) в виду его большей тяжеловесности (тяжеловесности XML), а также в виду распространения архитектурного стиля REST (REpresentational State Transfer) разработки программного обеспечения для распределенных систем. Хотя сам REST-стиль не требует использования JSON (он вообще, можно сказать, ничего не требует, а «рекомендует»), но как показывает практика, чаще при разработке REST API все-таки используется JSON для описания тела сообщений.
Так вот, практика разработки REST API с JSON–сообщениями прочно вошла в жизнь ИТ в России (и не только у нас), хотя опыт при этом по описанию структуры сообщений в виде XML Schema, значительно упростивший жизнь разработчиков web-служб в свое время, упорно игнорируется в случае c JSON–сообщениями. Но не всеми, что не может не радовать.
Когда разработчики, знакомые с XML Schema, столкнулись с необходимостью каждый раз заново изобретать велосипед с разбором документов и переизобретать логику валидации, сформировался аналогичный драфт JSON Schema. Он доступен по адресу json-schema.org, а также ряд документов по истории изменений и примеров использования. И несмотря на то что он публикуется в статусе «draft», его давно уже поддерживают все популярные платформы разработки и библиотеки на разных языках.
Сама JSON Schema предоставляет меньше возможностей по структуризации сообщений, чем XML Schema. То, что легко можно описать через XML Schema, не всегда будет тривиальной задачей повторить с помощью JSON Schema, если вообще это будет возможно. Но здесь же я бы данный факт стала рассматривать и как преимущество. Почему?
Известно, что чем проще и линейнее алгоритм работы системы, тем она и надежнее, что чем проще структура документа, тем легче он для восприятия и т.д.
Не могу удержаться, чтобы не процитировать: «Всё гениальное просто, и всё простое гениально». И если не удается с помощью схемы описать сложную структуру документа и множество допустимых вариантов, то, возможно, стоит посмотреть в сторону упрощения самой структуры и логики формирования этого документа?
Предисловие
Итак, о чем же эта статья?
Я бы хотела привлечь больше внимания к преимуществам описания передаваемых JSON сообщений схемой JSON Schema. Несмотря на то, что «на входе» разработка REST API без какой-либо JSON-схемы всегда проще и быстрее, но с ростом системы, ее отсутствие так или иначе приводит к удорожанию сопровождения и поддержки системы. Также любая предварительная проработка структуры сообщений способствует более качественной организации обмена сообщениями, без лишнего дублирования при обмене данными и общими правилами их обработки.
Также в целях распространения информации в русскоязычном сообществе о возможностях JSON Schema и правилах работы с ней я поделюсь своим некоторым опытом на конкретных примерах в рамках данной статьи.
Постановка задачи
Перед тем как приступить к изучению JSON и JSON Schema, опишу задачу, на которой мы будем рассматривать все примеры далее.
Рассмотрим ролевую модель управления в организации. Предполагаем, что справочную информацию по существующей ролевой модели мы должны будем передавать в зависимые системы в сообщениях в формате JSON посредством вызова REST-сервиса.
Описание задачи:
В организации есть сотрудники, им часто приходится работать одновременно в нескольких системах. При этом уровень доступа (полномочий) к тем или иным компонентам системы (ресурсам) для разных сотрудников в зависимости от их роли в организации может отличаться, и должен контролироваться при авторизации пользователя в системе.
Например, бухгалтер (роль) будет иметь доступ на чтение и редактирование (операции/полномочия) к расчетным листкам (ресурс) по заработной плате всех сотрудников, а у аналитика (роль), к примеру, будет доступ на чтение (операция/полномочия) только по своему расчетному листку (ресурс).
Необходимо спроектировать и описать ролевую модель управления в организации. Доступные роли, набор возможных полномочий и ресурсов в системе необходимо передавать другим системам по запросу.
Рисунок 1. Представление компонентов ролевой модели
Способы описания и реализации ролевой модели могут отличаться, но независимо от реализации чаще всего в ролевой модели в таком случае мы можем выделить следующие базовые составляющие:
- Роль (например, менеджер, бухгалтер и т.д.).
- Ресурс (например, документ, объект недвижимости и т.д.).
- Операция/полномочия (например, прочесть, распечатать, создать и т.д.).
При описании ролевого доступа (как один из возможных вариантов) прибегают к созданию дискретной матрицы доступа на основе выделенных сущностей, например:
Таблица 1. Дискретная матрица доступов.
Ресурс: documents | Ресурс: objects | |
---|---|---|
Роль: manager | read, print | read, create |
Роль: accountant | read, create | read |
Далее в статье мы ознакомимся сначала с теоретической составляющей текстового формата обмена данными JSON и правилами их структурирования с помощью JSON Schema, а в качестве примеров буду приводить описание сущностей-справочников для ролей, ресурсов и операций на языке JSON и их JSON–схем в рамках нашей поставленной задачи.
JavaScript Object Notation (JSON)
JSON (англ. JavaScript Object Notation) — текстовый формат обмена данными, основанный на JavaScript.
Теория
Язык разметки JSON задает ограниченный набор типов данных. Для пары {“ключ”: значение} для «ключа» всегда используют тип string, для «значения» применимы типы: string, number, object (тип JSON), array, boolean (true или false) и null.
Рисунок 2. Типы данных JSON
На рисунке приведены базовые типы и примеры их использования. Достаточно просто все, на мой взгляд.
Синтаксис JSON является подмножеством синтаксиса JavaScript, где:
- Данные записываются в виде пар {“ключ”: значение}.
- Данные разделяются запятыми.
- В фигурных скобках записываются объекты.
- В квадратных скобках записываются массивы.
- Наименования «ключей» регистрозависимы.
Рисунок 3. Синтаксис JSON
Практика
Рассмотрим пример справочника ролей, который мы будем передавать в сервисе:
Рисунок 4. Описание справочника ролей в формате json
Из примера видно, что даже несмотря на столь небольшое число базовых типов, при их комбинации мы можем создавать более сложные структуры сообщений при необходимости. Здесь, в частности, я описываю справочник ролей через объект массивов, содержащих другие объекты (на рисунке 4 выделены двумя прямоугольниками).
В табличном виде с помощью средств визуализации json-сообщений справочник может быть представлен следующим образом:
Рисунок 5. Визуализации справочника ролей в формате JSON
Справочник, условно говоря, представляет собой 3 «таблицы» для задания ролей в группе администраторов, бухгалтеров и рабочих. Состав «атрибутов» может быть расширен, при необходимости.
Визуальное представление, на мой взгляд, упрощает восприятие текстового описания. Аналогичную структуру зададим и для двух других справочников. Приведу ниже пример только табличного представления для справочника полномочий (операций) и ресурсов.
Рисунок 6. Визуализации справочника полномочий в формате JSON
Рисунок 7. Визуализации справочника ресурсов в формате JSON
Исходные сообщения в текстовом формате JSON для справочника ролей, ресурсов и полномочий можно скачать/просмотреть по ссылке.
Теперь перейдем к самому интересному: к изучению JSON Schema и созданию схемы под наши справочники!
JSON Schema
Теория
Поскольку схема json написана в формате JSON, она поддерживает все типы JSON плюс дополнение: тип integer, который является подтипом типа number. Сама схема является JSON-объектом и предназначена для описания данных в формате JSON. Ниже приводится схема типов данных, используемых при создании самой схемы:
Рисунок 8. Типы данных JSON Schema
Как видно из рисунка, для схемы используются все те же типы данных, а также все те же принципы синтаксиса, что и для обычного документа JSON, приведенные на рисунке 3.
Теперь рассмотрим самое важное — правила, используемые в схеме для задания ограничений и структурирования JSON-сообщений.
JSON Schema позволяет:
- Ограничить тип данных для элементов документа JSON.
- В зависимости от типа проверяемых данных, также могут быть применимы дополнительные правила — «keywords», начиная с корня схемы документа и спускаясь к их дочерним элементам.
Некоторые «ключевые слова» являются чисто описательными, как например: «title», «description» и др., которые просто описывают предназначение схемы. Другие предназначены для идентификации документа: «$schema». Это ключевое слово используется для указания желаемой версии схемы. Значение этого ключевого слова должно быть строкой, представляющей URI, например: "$schema": «json-schema.org/draft-04/schema#».
Здесь очень важно обратить внимание, что не все версии могут поддерживаться вашим инструментом работы со схемой. Но 4-й драфт поддерживают практически все. О последних изменениях (JSON Schema 2019-09 Release Notes) для разных версий можно познакомиться по ссылке json-schema.org/draft/2019-09/release-notes.html.
Остальные ключевые слова используются непосредственно для проверки документа JSON. Их мы сейчас и рассмотрим.
Таблица 2. Анализ структуры JSON Schema. Ключевые слова и их примеры использования.
Тип | Keyword(s) | Пример/описание |
---|---|---|
«Keywords» для описания схемы | "$schema" |
Используется для задания версии драфта схемы. |
|
Используется для указания уникального идентификатора документа или его подсхем. |
|
"title" |
|
|
Общие «Validation keywords», независимые от типа данных элемента | "enum" |
Выполняется проверка на совпадение с хотя бы 1 значением. |
"const" |
Выполняется проверка на точное соответствие заданному значению. |
|
"type" |
Указывает тип данных, который будет использоваться схемой. Это ключевое слово не является обязательным, и значением ключевого слова может быть строка, представляющая допустимый тип данных, или массив строк, представляющих допустимые типы данных. |
|
Keywords, зависимые от того типа данных, с которым они используются | "type": "string" |
Выполняется проверка на соответствие символьного выражения заданным параметрам. |
"type": "number" или "type": "integer" |
Выполняется проверка на соответствие числового выражения заданным параметрам. |
|
"type": "object" |
Объект проверяется как по наименованию ключа, так и по ограничениям на значение (в примере приведены не все ключевые слова). |
|
"type": "array" |
Массив проверяется как по наименованию ключа, так и по ограничениям на значение (в примере приведены не все ключевые слова). |
|
"type": "boolean" Ключевые слова не используются |
Тип boolean используется для проверки только логических значений (true или false). |
|
"type": "null" Ключевые слова не используются |
Тип null используется для проверки «отсутствия» значения. |
|
"type": "____" |
Ключевое слово format выполняет семантическую проверку данных. Поведение ключевого слова зависит от типа данных, т.е. одно и то же имя формата для строки ведет себя по-иному, чем для числа или отсутствует, поскольку не все типы данных должны реализовывать формат, и обычно разные типы данных имеют разные форматы. |
|
"type": "____" |
Значение по умолчанию для элемента. Это ключевое слово не является обязательным, и значение этого ключевого слова может быть любым. |
|
Ключевые слова для наложения условий на проверки | "not" |
Ключевые слова поддерживаются любым типом, являются необязательными. |
Ключевые слова, объединяющие проверки для разных частей схемы | "anyOf" |
Ключевые слова поддерживаются любым типом, являются необязательными. |
Ключевые слова, обеспечивающие ссылочность и связность схем | "$id" и "$ref" |
Схема RolesDictionaryDef.json:
Ссылаемся на схему 1 с помощью $ref (приведена часть схемы со ссылкой):
Мы рассматриваем 2 разных схемы JSON. Чтобы повторно использовать схему, мы делаем ссылку на нее с помощью ключевого слова $ref в другой схеме. |
"$ref" и "definitions" |
Через "$ref": "#/definitions/roles" ссылаемся на часть текущего документа, описанного после ключа «definitions»:
Для ссылок на проверки, относящихся только к текущему документу схемы. Реализуется с помощью ключевого слова $ref и ключевого слова definitions. |
|
"$ref" Абсолютные и относительные указатели. |
Абсолютный указатель используется для поиска, начиная с корня документа. Относительный используется для поиска значений, начиная с текущего местоположения. |
Мы рассмотрели ключевые слова схемы JSON, позволяющие описать нам будущую структуру наших сообщений в формате JSON.
Здесь вы можете найти больше примеров использования ключевых слов.
Практика
При рассмотрении примеров завершенных JSON-схем мы поступим аналогично примерам работы с самими сообщениями в формате JSON. Т.е. мы будем использовать визуальное представление в древовидном и табличном виде для наших схем справочников ролей, ресурсов и полномочий (операций), а с текстом схем предлагаю ознакомиться заинтересовавшихся читателей самостоятельно в git.
Ниже приводится схема для справочника ролей.
Рисунок 9. Пример JSON Schema для справочника ролей
Как мы видим на рисунке, схема представляет собой JSON-объект и описывает наше сообщение для передачи справочника ролей в формате JSON, которое приводилось на рисунке 4. На текущем примере представлено как с помощью JSON схемы может быть описан объект массивов, состоящий из объектов.
Схемы двух других справочников (полномочий и ресурсов) по своей структуре идентичны со схемой для справочника ролей, поэтому не буду их здесь приводить, а приведу схему, объединяющую в себе все 3 справочника.
К сожалению, схема всего справочника при раскрытии не поместится на экране, поэтому рассмотрим ее часть.
Рисунок 10. Пример JSON Schema справочника, объединяющего в себе справочник ролей, полномочий и ресурсов
На рисунке мы видим, что часть объектов массива справочников подключена с использованием ключевого слова «anyOf».
Также, возможно, нагляднее будет табличное представление справочника.
Рассмотрим еще одну важную особенность нашей схемы:
Рисунок 11. Пример JSON Schema справочника, объединяющего в себе справочник ролей, полномочий и ресурсов в табличном представлении
Из рисунка мы видим, что объединяющий справочник не дублирует в себе код из ранее разработанных справочников ролей, полномочий и ресурсов, а использует ключевое слово "$ref".
Справочники, рассматриваемые в примерах, находятся в одной директории, но, при необходимости, это правило можно не соблюдать, а размещать в разных директориях, указав корректно путь к ним при подключении. Данная возможность очень полезна, так как позволяет переиспользовать ранее созданные схемы, лишь подключая их в нужные структуры.
На этом мой обзор JSON и JSON Schema я завершаю. Надеюсь, что приведенный здесь материал и рассмотренные примеры, окажутся полезными при изучении возможностей JSON Sсhema.
Вместо заключения
Думаю, пора подводить итоги. Так что же применение JSON Schema в итоге нам может дать?
- Может упростить жизнь разработчикам и улучшить код по валидации JSON -сообщений.
Иными словами, это упрощение поддержки и интеграции ПО. - Позволит разрабатывать сервисы, прорабатывая форматы и состав данных с «заделом» на будущее развитие системы.
- Применить проверку документов в документо-ориентированных, объектно-ориентированных БД.
- JSON-Schema может помочь сэкономить время на тестировании и документировании API.
- Упрощение поддержки обратной совместимости API.
- Позволит управлять потоками данных.
- Гибкую валидацию при генерации JSON Schema в run-time со значениями в «enum», получаемыми на этапе выполнения программы.
Возможно применение для конечного автомата или workflow со статусами в «enum» (пример применения от NtsDK и VolCh). - JSON Schema может быть применена при реализации DTO
(пример использования от amarkevich)
Каждый из нас сам решает, «Быть или не быть JSON Schema» в наших IT -проектах. Выше я привела список того, что я считаю ключевым преимуществом применения схем, и ради чего уже стоит задуматься о ее применении в проектах.
Возможно, читатели захотят помочь мне продолжить этот список?
Я буду признательна :)
Также приведу список ссылок, на мой взгляд, полезных для работы с JSON и JSON Schema
- Официальный источник с примерами использования ключевых слов схемы.
- Источник с большим числом примеров использования ключевых слов схемы.
- Официальная страница стандарта (драфта).
- Список релизов (полезно для понимания динамики развития стандарта).
- Онлайн-валидатор с возможностью выбрать нужную версию драфта JSON-Schema.
- Открытый репозиторий JSON Schema
- JSON Schema для создания динамических интерфейсов, который может создаваться самим заказчиком (по рекомендации от alemiks), а также аналоги из мира angular ngx-schema-form, AJSF (по рекомендации от anotherpit).
И ссылку на git-репозиторий, где можно ознакомиться с исходными файлами, приводимыми для ознакомления в данной статье: репозиторий с исходными файлами примеров.
Системный архитектор,
© Ирина Блажина