Вводные
Нужно описать документацию на HTTP API, опубликовать в понятном заказчику и исполнителю виде.
Реализация
Для описания использую нотацию RAML (https://raml.org/), в результате получаю набор текстовых файлов которые удобно версионизировать в git. Для публикации использую формат HTML, .raml конвертирую в .hrml c помощью утилиты raml2html (https://github.com/raml2html/raml2html). С исходным текстом работаю используя Atom (https://atom.io/) и плагин API Workbench (https://atom.io/packages/api-workbench), в нем нормально работает автодополнение.
В средах разработки используется монофайл описания, что для меня не удобно (Обсуждение вопроса), проект из нескольких файлов можно собрать в один с помощью oas-raml-converter-cli (Ссылка на статью) более современный вариант webapi-parser (https://github.com/raml-org/webapi-parser).
Структура проекта
1. файл api.raml - корневой файл описания
2. папка dataTypes с описанием типов данных
3. папка resourceTypes с описанием типов ресурсов
4. папка securitySchemes - с схемами безопасности
Корневой файл
Обычно работаю с JSON форматом, поэтому в шапке описываю "mediaType: application/json", чтобы после в каждом ресурсе не уточнять.
Для всего API устанавливаю схему безопасности через "securedBy: token" (документация)
#%RAML 1.0
title: Example admin API
version: v1
baseUri: http://example.ru/
protocols: HTTPS
securitySchemes:
token: !include securitySchemes/token.raml
securedBy:
token
mediaType: application/json
Ресурс по работе со справочниками описываю так:
/products:
description: Работа со справочником Товары (в 1С номенклатура)
type: collection-item
get:
is: [filtered]
/{id}:
type: item
Далее опишу из чего он компонуется и как на выходе получается CRUD описание
Типы данных
Все типы данных (схемы данных) организую в виде библиотеки и подключаю её в корневой файл
Библиотека /dataTypes/library.raml
#%RAML 1.0 Library
types:
product: !include ProductType.raml
productResponse: !include ProductResponseType.raml
Описание типа dataTypes/ProductType.raml
#%RAML 1.0 DataType
displayName: Product
type: object
properties:
code:
type: string
description: Код номенклатуры
name:
type: string
description: Наименование
name_full:
type: string
description: Наименование полное
parent:
type: integer
description: Уникальный идентификатор родителя
required: false
По умолчанию все реквизиты объектов "required: true", что отражается в публикации документации.
Если используются guid то добавляю тип, чтобы регулярные выражения заработали через параметр "pattern" не получилось, поэтому разместил в описании.
GUID:
type: string
description: |
Globally Unique Identifier
Уникальный идентификатор из 1C
Regex
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
При размещении типа в корневом файле в файлах типов данных он может использоваться так:
ProductType c parent тип GUID
#%RAML 1.0 DataType
displayName: Product
type: object
properties:
code:
type: string
description: Код номенклатуры
name:
type: string
description: Наименование
name_full:
type: string
description: Наименование полное
parent:
type: GUID
description: Уникальный идентификатор родителя
required: false
При работе с объектом по API отправляю данные без идентификатора, получаю ответ с ним, для решения описания вопроса использую наследование объектов, добавляя нужные реквизиты к базовому типу
Описание типа dataTypes/ProductResponseType.raml
#%RAML 1.0 DataType
uses:
DataLib: library.raml
displayName: ProductResponse
type: DataLib.product
properties:
id:
type: integer
description: Уникальный идентификатор
Примеры типов данных
Обычно при разработке программисты запрашивают примеры. Их можно добавить в описание к типу, при публикации тип будет
#%RAML 1.0 DataType
displayName: OfferRemainsByStock
type: object
properties:
offer_xml_id:
type: GUID
description: ГУИД характеристики
stock_xml_id:
type: GUID
description: GUID склада
remains:
type: number
description: Остаток общий
free_remains:
type: number
description: Остаток свободный
shipping_remains:
type: OfferRemainsByStockShippingType[]
Описание типа табличной части остатков
#%RAML 1.0 DataType
displayName: OfferRemainsByStockShipping
type: object
properties:
count:
type: number
description: Остаток в пути
free:
type: number
description: Свободный остаток в пути
date:
type: number
description: Дата поступления
[
{
"offer_xml_id": "aa151fa8-babf-11e6-bf04-28e3479168d4",
"free_remains": 0,
"stock_xml_id": "641cda82-1d06-11e8-8266-74852a1a5b24",
"remains": 0,
"shipping_remains": [
{
"date": "2018-01-01T00:00:00",
"count": 10,
"free": 5
},
{
"date": "2018-02-01T00:00:00",
"count": 20,
"free": 10
}
]
},
{
"offer_xml_id": "aa151fa8-babf-11e6-bf04-28e3479168d4",
"free_remains": 1340,
"stock_xml_id": "5935a61d-f496-11e2-8f28-00142a859c1b",
"remains": 1340,
"shipping_remains": [
{
"date": "2018-01-01T00:00:00",
"count": 10,
"free": 5
},
{
"date": "2018-02-01T00:00:00",
"count": 20,
"free": 10
}
]
}
]
Типы ресурсов
Стандартный набор операций включает
- получение списка элементов, GET /
- получение элемента GET /{id}
- добавление элемента POST
- обновление элемента PUT /{id}
- удаление элемента DELETE /{id}
Описывать весь набор для каждого типа данных неудобно поэтому использую описание типов ресурсов (документация)
Использую описание для коллекции
/resourceTypes/collection-item.type.raml
#%RAML 1.0 ResourceType
description: Список элементов <<resourcePathName>>
get:
description: Получить список <<resourcePathName>>
responses:
200:
body:
type: object
properties:
result:
type: array
items: DataLib.<<resourcePathName | !singularize>>Response
400:
description: Ошибка в фильтрах
401:
body:
type: Response
post:
description: Создать новый <<resourcePathName | !singularize>>
body:
application/json:
type: DataLib.<<resourcePathName | !singularize>>
responses:
201:
body:
type: object
properties:
result:
type: DataLib.<<resourcePathName | !singularize>>Response
400:
body:
type: Response
401:
body:
type: Response
и для элемента
/resourceTypes/item.type.raml
#%RAML 1.0 ResourceType
description: Элемент <<resourcePathName | !singularize>>
uriParameters:
id:
type: integer
get:
description: Получить элемент <<resourcePathName | !singularize>>
responses:
200:
body:
type: object
properties:
result:
type: DataLib.<<resourcePathName | !singularize>>Response
400:
description: Данные некорректны
body:
type: Response
406:
description: Элемент не найден
put:
description: Сохранить элемент <<resourcePathName | !singularize>>
body:
type: DataLib.<<resourcePathName | !singularize>>
responses:
200:
body:
type: object
properties:
result:
type: DataLib.<<resourcePathName | !singularize>>Response
400:
description: Данные некорректны
body:
type: Response
406:
description: Элемент не найден
delete:
description: Удалить элемент <<resourcePathName | !singularize>>
responses:
200:
description: Успешно снят флаг активности
406:
description: Элемент не найден
Подключаю в корневом файле
resourceTypes:
collection-item: !include resourceTypes/collection-item.type.raml
item: !include resourceTypes/item.type.raml
При ошибке поиска выдаю ответ 406, чтобы он не пересекался с 404, который может выдать сервер перед API и будет ложное срабатывание при схеме
Если GET /{id}=200 Тогда PUT /{id} Иначе POST /
Формат ответа об ошибке один и тот же поэтому использую для этого отдельный тип
/dataTypes/ResponseType.raml
#%RAML 1.0 DataType
displayName: Response
type: object
properties:
status: string
result:
type: array
items: string
required: false
error:
type: array
items: string
required: false
Для использования имени url для получения типа данных использую "<<resourcePathName" (документация)
В результате состыковки будет подключен тип
properties:
result:
type: array
items: DataLib.productResponse
Особенности
Для некоторых ресурсов нужна пагинация или фильтрация, её можно организовать в traits (особенности) (документация)
Особенность фильтрации и пагинаци
traits:
filtered:
queryParameters:
date_update_from:
type: datetime
description: Дата обновления больше чем указано
start:
type: integer
default: 1
description: Стартовая позиция
count:
type: integer
default: 50
maximum: 100
description: Количество элементов на странице
и подключить её в ресурсе
get:
is: [filtered]
Итог
В результате компоновки инструментов можно быстро и качественно писать документацию на HTTP API и опубликовать
Внешний вид опубликованного API
Внешний вид описания ресурса
Благодарю за внимание.