JSON Schema. Быть или не быть?

в 6:29, , рубрики: Access Control Matrix, api, IT-стандарты, javascript, json, open source, Role based access control, Проектирование и рефакторинг, ролевая модель

Архитектура: искусство делать излишнее необходимым.

Фредерик Кислер

Ни для кого давно уже не секрет, что для любого 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-сервиса.

Описание задачи:

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

Например, бухгалтер (роль) будет иметь доступ на чтение и редактирование (операции/полномочия) к расчетным листкам (ресурс) по заработной плате всех сотрудников, а у аналитика (роль), к примеру, будет доступ на чтение (операция/полномочия) только по своему расчетному листку (ресурс).

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

JSON Schema. Быть или не быть? - 1
Рисунок 1. Представление компонентов ролевой модели

Способы описания и реализации ролевой модели могут отличаться, но независимо от реализации чаще всего в ролевой модели в таком случае мы можем выделить следующие базовые составляющие:

  1. Роль (например, менеджер, бухгалтер и т.д.).
  2. Ресурс (например, документ, объект недвижимости и т.д.).
  3. Операция/полномочия (например, прочесть, распечатать, создать и т.д.).

При описании ролевого доступа (как один из возможных вариантов) прибегают к созданию дискретной матрицы доступа на основе выделенных сущностей, например:

Таблица 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.

JSON Schema. Быть или не быть? - 2
Рисунок 2. Типы данных JSON

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

Синтаксис JSON является подмножеством синтаксиса JavaScript, где:

  1. Данные записываются в виде пар {“ключ”: значение}.
  2. Данные разделяются запятыми.
  3. В фигурных скобках записываются объекты.
  4. В квадратных скобках записываются массивы.
  5. Наименования «ключей» регистрозависимы.

JSON Schema. Быть или не быть? - 3
Рисунок 3. Синтаксис JSON

Практика

Рассмотрим пример справочника ролей, который мы будем передавать в сервисе:

JSON Schema. Быть или не быть? - 4
Рисунок 4. Описание справочника ролей в формате json

Из примера видно, что даже несмотря на столь небольшое число базовых типов, при их комбинации мы можем создавать более сложные структуры сообщений при необходимости. Здесь, в частности, я описываю справочник ролей через объект массивов, содержащих другие объекты (на рисунке 4 выделены двумя прямоугольниками).

В табличном виде с помощью средств визуализации json-сообщений справочник может быть представлен следующим образом:

JSON Schema. Быть или не быть? - 5
Рисунок 5. Визуализации справочника ролей в формате JSON

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

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

JSON Schema. Быть или не быть? - 6
Рисунок 6. Визуализации справочника полномочий в формате JSON

JSON Schema. Быть или не быть? - 7
Рисунок 7. Визуализации справочника ресурсов в формате JSON

Исходные сообщения в текстовом формате JSON для справочника ролей, ресурсов и полномочий можно скачать/просмотреть по ссылке.
Теперь перейдем к самому интересному: к изучению JSON Schema и созданию схемы под наши справочники!

JSON Schema

Теория

Поскольку схема json написана в формате JSON, она поддерживает все типы JSON плюс дополнение: тип integer, который является подтипом типа number. Сама схема является JSON-объектом и предназначена для описания данных в формате JSON. Ниже приводится схема типов данных, используемых при создании самой схемы:

JSON Schema. Быть или не быть? - 8
Рисунок 8. Типы данных JSON Schema

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

Теперь рассмотрим самое важное — правила, используемые в схеме для задания ограничений и структурирования JSON-сообщений.

JSON Schema позволяет:

  1. Ограничить тип данных для элементов документа JSON.
  2. В зависимости от типа проверяемых данных, также могут быть применимы дополнительные правила — «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"
"$schema": http://json-schema.org/draft-04/schema#

Используется для задания версии драфта схемы.

"$id"
"$id": "http://archi-blair.com/schemas/RolesDictionaryDef.json#"

Используется для указания уникального идентификатора документа или его подсхем.

"title"
"description"
"examples"
"comment"
{
"title": "JSON schema for dictionary",
"description": "Справочники ролей",
"examples": ["user", "manager"],
"comment": "не поленитесь прочесть статью до конца))"
}

Общие «Validation keywords», независимые от типа данных элемента "enum"
{"enum": [ "administrator", "superuser" ]}

Выполняется проверка на совпадение с хотя бы 1 значением.

"const"
{"const": "user" }

Выполняется проверка на точное соответствие заданному значению.

"type"
{"type": ["number", "string", "null", "boolean"]}
{"type": "array"}

Указывает тип данных, который будет использоваться схемой. Это ключевое слово не является обязательным, и значением ключевого слова может быть строка, представляющая допустимый тип данных, или массив строк, представляющих допустимые типы данных.

Keywords, зависимые от того типа данных, с которым они используются "type": "string"

minLength
maxLength
pattern
contentEncoding
contentMediaType

{
"type": "string",
"minLength": 3,
"maxLength": 10,
"pattern": "^test\/[a-z-]+$",
"contentEncoding": "base64",
"contentMediaType": "application/json"
}

Выполняется проверка на соответствие символьного выражения заданным параметрам.

"type": "number" или "type": "integer"

minimum
exclusiveMinimum
maximum
exclusiveMaximum
multipleOf

{
  "type": "number",
  "minimum": 1.5,
   "exclusiveMinimum": true,
   "maximum": 12.3,
   "exclusiveMaximum": true,
   "multipleOf": 0.5
}

Выполняется проверка на соответствие числового выражения заданным параметрам.

"type": "object"

properties
required
dependencies
minProperties
maxProperties
propertyNames
patternProperties
additionalProperties


"employees": {
 "description": "Сотрудники",
 "type": "array",
  "uniqueItems": true,
   "items": {
     "type": "object",
     "properties": {
           "name": {
               "type": "string",
               "enum": ["employee"],
               "enumNames": ["Сотрудник"]
	    },
            "enabled": {
                "type": "boolean",
                "default": true
             }
    },
 "additionalProperties": false
 }
}

Объект проверяется как по наименованию ключа, так и по ограничениям на значение (в примере приведены не все ключевые слова).

"type": "array"

minItems
maxItems
uniqueItems
contains
items
additionalItems


"employees": {
 "description": "Сотрудники",
 "type": "array",
 "uniqueItems": true,
 "items": {
    "type": "object",
    "properties": {
          "name": {
               "type": "string",
               "enum": ["employee"],
               "enumNames": ["Сотрудник"]
            },
          "enabled": {
                "type": "boolean",
                "default": true
            }
     },
 "additionalProperties": false
 }
}

Массив проверяется как по наименованию ключа, так и по ограничениям на значение (в примере приведены не все ключевые слова).

"type": "boolean"
Ключевые слова не используются
{"type": "boolean"}

Тип boolean используется для проверки только логических значений (true или false).

"type": "null"
Ключевые слова не используются
{"type": "null"}

Тип null используется для проверки «отсутствия» значения.

"type": "____"
"format": "____"
{
  "type": "string",
  "format": "date"
}

Ключевое слово format выполняет семантическую проверку данных. Поведение ключевого слова зависит от типа данных, т.е. одно и то же имя формата для строки ведет себя по-иному, чем для числа или отсутствует, поскольку не все типы данных должны реализовывать формат, и обычно разные типы данных имеют разные форматы.

"type": "____"
"default": "____"
{
"enabled": {
	"type": "boolean",
	"default": true
}	

Значение по умолчанию для элемента.

Это ключевое слово не является обязательным, и значение этого ключевого слова может быть любым.

Ключевые слова для наложения условий на проверки "not"
"if-then-else"
{
  "not": {
    "type": "string"
  }
}

Ключевые слова поддерживаются любым типом, являются необязательными.

Ключевые слова, объединяющие проверки для разных частей схемы "anyOf"
"oneOf"
"allOf"
{
  "type": "string",
  "anyOf": [
    {"const": "user"},
    {"const": "manager" }
  ]
}

Ключевые слова поддерживаются любым типом, являются необязательными.

Ключевые слова, обеспечивающие ссылочность и связность схем "$id" и "$ref" Схема RolesDictionaryDef.json:

{
"$id": http://archi-blair.com/schemas/RolesDictionaryDef.json#
}

Ссылаемся на схему 1 с помощью $ref (приведена часть схемы со ссылкой):


"items": {
"type": "object",
"minLength": 1,
"properties": {
  "name": {
  "description": "Наименование группы словарей",
  "type": "string"
   },
"description": {
  "description": "Описание словарей",
  "type": "string"
 },
"dictionaryGroup": {
  "$ref": "RolesDictionaryDef.json#/definitions/roles"
    }
},
"additionalProperties": false
}

Мы рассматриваем 2 разных схемы JSON. Чтобы повторно использовать схему, мы делаем ссылку на нее с помощью ключевого слова $ref в другой схеме.

"$ref" и "definitions" Через "$ref": "#/definitions/roles" ссылаемся на часть текущего документа, описанного после ключа «definitions»:

{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Справочники ролей",
"type": "object",
"properties": {
"dictionaries": {
   "description": "Справочники",
   "type": "array",
   "maxItems": 1,
   "items": {
      "type": "object",
      "minLength": 1,
      "properties": {
               "name": {
                  "description": "Наименование словаря",
                  "type": "string",
                  "enum": [
                     "Roles Dictionary"
                     ]
                 }
       "dictionaryGroups": {
             "$ref": "#/definitions/roles",
             "description": "Справочные данные"
          }
      },
 "additionalProperties": false
   }
  }
 },
 "additionalProperties": false,
     "definitions": {
          "roles": {
             "description": "Роли",
             "type": "object",
             "properties": {
             "administrators": {
             "description": "Администраторы",
             "type": "array",
             "uniqueItems": true,
             "items": {
             "type": "object",
             "properties": {
                   "name": {
                      "type": "string",
                      "enum": [
                         "administrator", 
                         "superuser"
                          ],
                        "enumNames": [
                          "Администратор", 
                          "Пользователь-админ с ограниченным доступом"
                           ]
                         },
                    "enabled": {
                    "type": "boolean",
                    "default": true
                  }
              },
    "additionalProperties": false
      }
    }			
   },
  "additionalProperties": false
  }
 },
 "$id": "http://archi-blair.com/schemas/RolesDictionaryDef.json#"
}

Для ссылок на проверки, относящихся только к текущему документу схемы. Реализуется с помощью ключевого слова $ref и ключевого слова definitions.

"$ref"
Абсолютные и относительные указатели.
"$ref": " #/definitions/roles"
"$ref": "RolesDictionaryDef.json#/definitions/roles"

Абсолютный указатель используется для поиска, начиная с корня документа.
Относительный используется для поиска значений, начиная с текущего местоположения.

Мы рассмотрели ключевые слова схемы JSON, позволяющие описать нам будущую структуру наших сообщений в формате JSON.

Здесь вы можете найти больше примеров использования ключевых слов.

Практика

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

Ниже приводится схема для справочника ролей.

JSON Schema. Быть или не быть? - 9
Рисунок 9. Пример JSON Schema для справочника ролей

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

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

К сожалению, схема всего справочника при раскрытии не поместится на экране, поэтому рассмотрим ее часть.

JSON Schema. Быть или не быть? - 10
Рисунок 10. Пример JSON Schema справочника, объединяющего в себе справочник ролей, полномочий и ресурсов

На рисунке мы видим, что часть объектов массива справочников подключена с использованием ключевого слова «anyOf».

Также, возможно, нагляднее будет табличное представление справочника.

Рассмотрим еще одну важную особенность нашей схемы:

JSON Schema. Быть или не быть? - 11
Рисунок 11. Пример JSON Schema справочника, объединяющего в себе справочник ролей, полномочий и ресурсов в табличном представлении

Из рисунка мы видим, что объединяющий справочник не дублирует в себе код из ранее разработанных справочников ролей, полномочий и ресурсов, а использует ключевое слово "$ref".

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

На этом мой обзор JSON и JSON Schema я завершаю. Надеюсь, что приведенный здесь материал и рассмотренные примеры, окажутся полезными при изучении возможностей JSON Sсhema.

Вместо заключения

Думаю, пора подводить итоги. Так что же применение JSON Schema в итоге нам может дать?

  1. Может упростить жизнь разработчикам и улучшить код по валидации JSON -сообщений.

    Иными словами, это упрощение поддержки и интеграции ПО.

  2. Позволит разрабатывать сервисы, прорабатывая форматы и состав данных с «заделом» на будущее развитие системы.
  3. Применить проверку документов в документо-ориентированных, объектно-ориентированных БД.
  4. JSON-Schema может помочь сэкономить время на тестировании и документировании API.
  5. Упрощение поддержки обратной совместимости API.
  6. Позволит управлять потоками данных.

Каждый из нас сам решает, «Быть или не быть JSON Schema» в наших IT -проектах. Выше я привела список того, что я считаю ключевым преимуществом применения схем, и ради чего уже стоит задуматься о ее применении в проектах.

Возможно, читатели захотят помочь мне продолжить этот список?
Я буду признательна :)

Также приведу список ссылок, на мой взгляд, полезных для работы с JSON и JSON Schema

  1. Официальный источник с примерами использования ключевых слов схемы.
  2. Источник с большим числом примеров использования ключевых слов схемы.
  3. Официальная страница стандарта (драфта).
  4. Список релизов (полезно для понимания динамики развития стандарта).
  5. Онлайн-валидатор с возможностью выбрать нужную версию драфта JSON-Schema.
  6. Открытый репозиторий JSON Schema
  7. JSON Schema для создания динамических интерфейсов, который может создаваться самим заказчиком (по рекомендации от alemiks).

И ссылку на git-репозиторий, где можно ознакомиться с исходными файлами, приводимыми для ознакомления в данной статье: репозиторий с исходными файлами примеров.

Системный архитектор,
© Ирина Блажина

Автор: Archi-Blair

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js