Статьи

Документируйте свою схему JSON API с помощью PRMD

В недавнем проекте моя команда согласилась, что нам нужен какой-то способ для проверки и документирования JSON, входящего и выходящего из нашего API. Мы уже остановились на использовании JSON Schema в качестве средства описания API, поэтому определение того, как проверять (и документировать) JSON Schema, было следующим в списке дел. Я провел множество исследований о том, как лучше всего выполнить такие благородные задачи. Хорошо, хорошо, «тонны исследований» действительно означают «искать драгоценные камни, которые уже делают это». Я также проконсультировался по своему прошлому опыту с такими вещами, как Grape и Swagger, но мы здесь не используем Grape, и я не смог найти ничего, что позволило бы мне легко включить Swagger. Даже если бы я сделал, это только документы, без и проверки.

Короче говоря (это «разговор старца» для TL; DR), если вы хотите документировать свой API, проверять запросы и ответы и использовать схему JSON, это немного трудоемко. Существуют подходы, такие как iodocs , но я не хотел устанавливать Redis, использовать Node и т. Д. Только для получения документов. Кроме того, существует множество инструментов для создания документов, когда у вас есть схема, но они не сильно помогают в ее создании. Я быстро понял, что независимо от того, какой инструмент или направление я выбрал, для этого потребуется много усилий. Я надеюсь, что эта статья поможет кому-то еще с той же целью продвинуться по пути.

Ранее в этом году я долго и пристально рассматривал pliny как возможную платформу для наших API. Плиний происходит от хороших людей из Heroku, которые немного знают об API. Он основан на Sinatra и поставляется с некоторыми самоуверенными (но очень хорошими) помощниками для таких вещей, как ведение журнала, отслеживание запросов, управление версиями и т. Д. Я настоятельно рекомендую вам проверить это, если вы пишете API на Ruby. Я сделал, и в итоге я отвечал на наши потребности JSON Schema.

Pliny использует два других драгоценных камня из пользовательского агента Github: prmd и Committee . Вместе эти два гем-тега объединяют ваши потребности в JSON-схеме / API. prmd фокусируется на создании схемы JSON и генерации документации API, и это тема этого поста. Комитет представляет собой набор методов и промежуточного программного обеспечения Rack для проверки вашей схемы. Последующий пост будет посвящен Комитету.

статья

Схема JSON и Гипер схема JSON

Схема JSON — это одна (из очень многих) попыток создать способ определения структуры данных JSON. Цель состоит в том, чтобы иметь возможность документировать и проверять JSON-провайдер / репозиторий, как если бы вы использовали схему базы данных. Существуют спецификации (в настоящее время на черновой версии 4), но я предпочитаю эту онлайн-книгу Майкла Дроттбума из Научного института космического телескопа (БОЛЬШОЕ название) в качестве отправной точки.

JSON Hyper Schema превращает вашу схему в «гипертекст». Другими словами, JSON Hyper Schema описывает конечные точки для вашего приложения, включая то, что оно примет и предоставит. Книга Космического телескопа не охватывает гиперсхему JSON, поэтому я предлагаю вам отсканировать спецификацию и прочитать этот пост .

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

Я не собираюсь проходить через спецификацию, поэтому я призываю вас прочитать эту книгу. Я предполагаю, что вы понимаете основы JSON Schema и Hyper Schema до конца этой статьи.

PRMD

Тэг PRMD — «Инструменты JSON-схемы и генерация документов для HTTP API». Другими словами, prmd позволяет вам генерировать схему JSON, которую затем необходимо вручную изменить / настроить в соответствии с вашим API. Как только схема полностью определена, prmd предоставляет задачи для проверки соответствия схемы JSON, а также для создания документов для этого API.

Конечная игра использования prmd — иметь определенную схему JSON для вашего API вместе с сопроводительной документацией. Документация генерируется в формате Markdown , что приятно.

prmd предоставляет исполняемый файл ( prmd ) со следующими командами:

 init: Scaffold resource schemata combine: Combine schemata and metadata into single schema verify: Verify a schema doc: Generate documentation from a schema render: Render views from schema 

Мы будем напрямую использовать каждый из них, напрямую или через задачу Rake, за исключением render , который я не использовал.

пример

В этом посте наше приложение обеспечивает создание аккаунта (регистрацию) и аутентификацию. Мы определим ресурс ( account ) и конечные точки ( links ). Я использую здесь Rails, но Pliny основан на Sinatra, поэтому вы сможете легко использовать концепции, описанные здесь, в любой веб-среде Ruby. Схема также будет предоставлять конечные точки для сеансов и сброса пароля.

Приложение Rails использует rails-api , начиная с Ruby 2.2.2, Rails 4.2.3. У меня есть репозиторий, который я использую, так что вы можете проверить его, чтобы увидеть, как он настроен.

Настройка PRMD

prmd нужен каталог для хранения схемы и поддерживающих ее файлов. Создайте каталог схем / схем :

 mkdir -p schema/schemata 

Файлы схемы верхнего уровня будут жить в схеме, а отдельные файлы схемы (например, для account ) будут жить в схеме / схемах . Это просто наше соглашение, поэтому вы можете делать то, что вам нравится.

Файлы «верхнего уровня», о которых я упоминал ранее, — это файлы, описывающие метаданные нашего общего API, не связанные с конкретным ресурсом. Вот наши:

 { "description": "Account API", "id": "account-api", "links": [{ "href": "https://accounts.ourapi.com", "rel": "self" }], "title": "Accounts" } 

Это взято из prmd README и удовлетворяет требованиям метаданных схемы JSON.

Мне кажется, я должен упомянуть, что вы можете использовать JSON или YAML для определения вашего API. Я начал с YAML и мне это не понравилось. Я предпочитаю делать это в формате JSON. Одним из дополнительных преимуществ этого в JSON является то, что вы можете помещать вещи в различные онлайн-линкеры JSON и JSON Schema и ловить опечатки или потерянные скобки.

Схема аккаунта

Время для создания схемы схемы:

 prmd init account > schema/schemata/account.json 

Это создает базовую схему JSON для ресурса нашего аккаунта. К сожалению, она ничего не извлекает из нашей модели, если она у нас есть (и, вероятно, не должна), поэтому схему JSON необходимо настроить вручную, чтобы соответствовать API. Откройте этот файл и быстро прочитайте его. Я рассечу его, раздел за разделом, так что мы на одной странице.

Общая информация о ресурсах

 { "$schema": "http://json-schema.org/draft-04/hyper-schema", "title": "Account", "description": "The Account resource for the API", "stability": "prototype", "strictProperties": true, "type": [ "object" ], .... 

Как видите, $schema является черновиком 4 гиперсхемы JSON. Есть пара «FIXME», которые должны быть рассмотрены в title и description , которые я исправил выше. stability определяет стабильность (дух) ресурса и относится к prototype, development, or production . strictProperties указывает, что этот объект (ресурс) имеет ТОЛЬКО свойства, определенные в этом object . Существует additionalProperties свойствоProperties, которое является взаимоисключающим со strictProperties .

Определения

definitions являются ссылочными свойствами, которые будут использоваться во всей схеме, поэтому нет необходимости постоянно переопределять id или email в контексте определения схемы. Вот как вы СУШИТЕ вещи в схеме.

Сгенерированные определения имеют примеры, такие как name и id . Я изменил приведенный ниже фрагмент, чтобы он соответствовал нашему ресурсу, заменив name на email и добавив password :

 "definitions": { "id": { "description": "unique identifier of account", "readOnly": true, "format": "uuid", "type": [ "string" ] }, "email": { "description": "unique email of account", "readOnly": true, "type": [ "string" ] }, "password": { "description": "account password", "readOnly": true, "type": [ "string" ] } "identity": { "anyOf": [ { "$ref": "/schemata/account#/definitions/id" }, { "$ref": "/schemata/account#/definitions/email" } ] }, "created_at": { "description": "when account was created", "format": "date-time", "type": [ "string" ] }, "updated_at": { "description": "when account was updated", "format": "date-time", "type": [ "string" ] } } 

Некоторые важные выводы из definitions :

  • Каждое определение имеет type который представляет тип данных. Допустимые значения: "array", "boolean", "integer", "number", "null", "object", "string" . format — это то, что вы думаете, и возможные значения: "date-time", "email", "hostname", "ipv4", "ipv6", "uri" . На самом деле prmd предоставляет пользовательский uuid , который вы можете (должны) использовать для идентификаторов.
  • Говоря об идентификации, свойство identity — это то, как ресурс идентифицирует конкретные экземпляры. В этом случае мы можем использовать id или email . Свойство $ref является ссылкой на свойство в definitions . Итак, "$ref": "/schemata/account#/definitions/email" включает наше определение электронной почты (технически называемое «разыменование»).

Остальные свойства должны быть самоочевидными.

связи

Раздел links является частью спецификации JSON Hyper Schema и объясняет конечные точки, поддерживаемые схемой:

 "links": [ { "description": "Create a new account.", "href": "/accounts", "method": "POST", "rel": "create", "schema": { "properties": { }, "type": [ "object" ] }, "title": "Create" }, { "description": "Delete an existing account.", "href": "/accounts/{(%2Fschemata%2Faccount%23%2Fdefinitions%2Fidentity)}", "method": "DELETE", "rel": "destroy", "title": "Delete" }, { "description": "Info for existing account.", "href": "/accounts/{(%2Fschemata%2Faccount%23%2Fdefinitions%2Fidentity)}", "method": "GET", "rel": "self", "title": "Info" }, { "description": "List existing accounts.", "href": "/accounts", "method": "GET", "rel": "instances", "title": "List" }, { "description": "Update an existing account.", "href": "/accounts/{(%2Fschemata%2Faccount%23%2Fdefinitions%2Fidentity)}", "method": "PATCH", "rel": "update", "schema": { "properties": { }, "type": [ "object" ] }, "title": "Update" } ], 

link может включать href , method , schema и targetSchema . Последние два свойства — это то, что API будет принимать и предоставлять соответственно. rel — это отношение ссылки на ресурс, и оно должно быть одним из следующих: create , destroy , self , create instances или update .

Я уверен, что вы видите часть этой рвоты JSON в href для конкретных ссылок. Я говорю о таких вещах, как:

 "href": "/accounts/{(%2Fschemata%2Faccount%23%2Fdefinitions%2Fidentity)}", 

По сути, это действительно /accounts/{identity} . Помните, когда мы определили identity в разделе definitions ? Эта длинная, странная строка — это просто закодированный URL /schemata/account/#definitions/identity .

Вы можете видеть, что prmd в основном генерировал «типичные» RESTful-ссылки, создавая CRUD-подобный API. В некоторых случаях это хорошее начало. В этом случае нам нужно внести некоторые изменения. Так как это для регистрации и аутентификации:

  • Давайте избавимся от «Списка существующих учетных записей», в любом случае это похоже на проблему безопасности.
  • Добавить ссылки на session и потоки password .
  • Вход в систему будет выполняться через POST в /accounts/session , он принимает параметр remember_me и возвращает token .
  • Конечные точки пароля потребуют токен сброса.
  • Я собираюсь довольно подробно рассказать, как я определяю, что ссылки принимают и возвращают.

В качестве примера приведем схему создания / регистрации нового аккаунта:

 { "description": "Create a new account.", "href": "/account", "method": "POST", "rel": "create", "schema": { "properties": { "account" : { "type" : "object", "properties": { "email" : { "$ref": "/schemata/account#/definitions/email" }, "password": { "type": "string", "description": "The password" }, "remember_me": { "type": "boolean", "description": "True/false - generate refresh token (optional)" } }, "required" : [ "email", "password" ] } }, "type": [ "object" ] }, "title": "Create", "targetSchema": { "type": "object", "properties": { "token" : { "$ref": "/schemata/account#/definitions/token" } } } } 

Итак, что изменилось?

  • Я расширил schema/properties чтобы получить account с email , password и необязательным свойством remember_me . Электронная почта и пароль являются ссылками на те же элементы в definitions , но свойство remember_me определяется локально.
  • Обратите внимание на required параметры, которые вложены в учетную запись. Это позволяет вам определять обязательные и дополнительные параметры на любом уровне.
  • Я добавил свойство targetSchema , определяющее, что будет возвращено при вызове. В данном случае это добавленное мной определение token , которое выглядит следующим образом:

     token": { "type": "string", "description": "The token", "example" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJkYXRhIjp7ImlkIjoiMTE0MzYiLCJ0eXBlIjoiYWNjb3VudHMiLCJhdHRyaWJ1dGVzIjp7ImVtYWlsIjoiZ2xlbm4uZ29vZHJpY2hAZ21haWwuY29tIn19LCJzdWIiOiJhY2NvdW50IiwiZXhwIjoxNDM3MjM0OTM0LCJpc3MiOiJVbmlxdWUgVVNBIiwiaWF0IjoxNDM3MTQ4NTM0LCJqdGkiOiI3ZmJiYTgzOS1kMGRiLTQwODItOTBmZC1kNmMwM2YwN2NmMWMifQ.SuAAhWPz_7VfJ2iyQpPEHjAnj_aZ-0-gI4uptFucWWflQnrYJl3Z17vAjypiQB_6io85Nuw7VK0Kz2_VHc7VHZwAjxMpzSvigzpUS4HHjSsDil8iYocVEFlnJWERooCOCjSB9R150Pje1DKB8fNeePUGbkCDH6QSk2BsBzT07yT-7zrTJ7kRlsJ-3Kw2GDnvSbb_k2ecX_rkeMeaMj3FmF3PDBNlkM" }, 

Вау! Это безобразно Да, это так. Тем не менее, этот example будет использоваться при создании документации по уценке, поэтому сейчас я возьму некрасиво, чтобы получить ее позже.

В производственном приложении есть много таких изменений, чтобы получить четкую схему. Это ручной, утомительный процесс, который никто не любит делать. Однако стоит иметь документы и возможность проверять схему JSON в тестах и ​​на рабочей площадке. По крайней мере, мы так думаем. Кроме того, наши API ориентированы (например, на микросервисы), поэтому сфера действия каждой схемы JSON меньше, чем, скажем, для большого монолитного API.

Док Поколение

Часть вознаграждения, которое приходит от кропотливого определения схемы JSON, — это «простая» документация API. И да, я понимаю, что есть другие способы сделать это (RAML, пасека и т. Д.), Поэтому, если у вас есть хороший способ сделать это, я не буду отговаривать вас от этого.

Хотя prmd предлагает исполняемый файл, мне нравятся задачи Rake для объединения файлов схемы и генерации документов. README объясняет, как создавать задачи Rake. Короче говоря, я создал lib / tasks / schema.rake со следующим:

 require "prmd/rake_tasks/combine" require "prmd/rake_tasks/verify" require "prmd/rake_tasks/doc" namespace :schema do Prmd::RakeTasks::Combine.new do |t| t.options[:meta] = "schema/meta.json" # use meta.yml if you prefer YAML format t.paths << "schema/schemata" t.output_file = "schema/authentication-api.json" end Prmd::RakeTasks::Verify.new do |t| t.files << "schema/authentication-api.json" end Prmd::RakeTasks::Doc.new do |t| t.files = { "schema/authentication-api.json" => "schema/authentication-api.md" } end task default: ["schema:combine", "schema:verify", "schema:doc"] end 

Обратите внимание, что я изменил некоторые пути и имена файлов из примеров в README, чтобы соответствовать этому проекту. Теперь я могу идти

 rake schema:combine rake schema:verify rake schema:doc or rake schema 

Если у вас есть ошибки JSON, combine не удастся. Здесь я удалил : в документе и получил:

 unable to parse schema/schemata/account.json (#<JSON::ParserError: 795: unexpected token at '{ "$schema": "http://json-schema.org/draft-04/hyper-schema", "title": "Authentication API - Account", "description": "The Account Schema", "stability": "prototype", "strictProperties": true, "type": [ "object" ], "definitions": { "id": { "description": "unique identifier of account", "readOnly": true, ... Somes files have failed to parse. If you wish to continue without them,please enable faulty_load using --faulty-load 

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

Если у вас есть ошибки JSON Schema, тогда задача verify не будет выполнена. Здесь я password тип password :

 schema/authentication-api.json: #/definitions/account/links/1/schema/properties/account/properties/password/type: failed schema #/properties/type: No subschema in "anyOf" matched. schema/authentication-api.json: #/definitions/account/links/0/schema/properties/account/properties/password/type: failed schema #/properties/type: No subschema in "anyOf" matched. schema/authentication-api.json: #/definitions/account/links/0/schema/properties/account/properties/password/type: failed schema #/properties/type: No subschema in "anyOf" matched. schema/authentication-api.json: #/definitions/account/links/0/schema/properties/account/properties/password: failed schema #/properties/properties/additionalProperties: Not all subschemas of "allOf" matched. 

Предполагая, что combine работает, а verify ничего не находит, затем doc с файлом schema / authentication-api.md . Вот фрагмент кода:

1

Полные схемы документов можно найти здесь .

Я могу добавить эту документацию по уценке в Github Repo или добавить маршрут, который использует что-то вроде Redcarpet для создания HTML. Важным моментом является наличие документов. Если ваша команда использует Github, ими тоже легко поделиться.

Но ждать! Есть еще кое-что!

Я знаю, о чем ты думаешь. «Почему этот парень прошел через все это для какой-то нормальной документации уценки? Разве он не слышал о Swagger? »Я слышал об этом, и он не будет делать то, что я хочу. Я не думаю Я упоминал об использовании схемы в спецификации / тестах для проверки схемы JSON, а также об использовании промежуточного программного обеспечения для проверки запросов на основе того, что схема примет. Это выходит за рамки того, что предлагает prmd. Тем не менее, люди, стоящие за prmd, пишут жемчужину комитета именно для этой цели. И это будет темой моего следующего поста. Вы можете использовать время между сейчас и потом, чтобы привести свою схему в порядок. 😉