Привет, Хабр! Меня зовут Виктория Юльская, и я старший системный аналитик в Ozon.

Я думаю, здесь найдётся много людей, которые хоть раз работали с документацией API в Confluence. Да-да, те самые километровые страницы на каждый метод — с описанием всего и вся в виде текста, таблиц, диаграмм последовательности и т. д.

Зачастую такая документация API в Confluence устаревает ровно в тот момент, как её закончили писать. После передачи задачи в разработку, как только что-то непонятно, куд�� все идут? Правильно, к аналитику — «А как это работает? А что это значит? А что если...?».

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

Есть ли более эффективный способ ведения и поддержания документации API в актуальном состоянии? Давайте разбираться.

Немного вводной части

Сгенерировать спецификацию/документацию можно из аннотаций в коде, и многие думают, что генерация спецификации из кода приводит к меньшему отклонению документации, т.к. она тесно связана с кодом. Но всё-таки считается, что это не лучший подход.

То, о чём мы сегодня с вами поговорим, наилучшим образом ложится на подход Specification-first / Manifest First / Design API First — называйте как хотите, но суть одна — сначала спецификация, потом код!

Эта тема достаточно холиварная и зачастую сталкивает лбами системных аналитиков и разработчиков :) Тем не менее подход не новый и давно уже показал себя как вполне себе эффективный.

Не будем сильно погружаться в сравнение подходов и как сделать правильный выбор, но в двух словах подсветим преимущества подхода Design API First:

  • Согласованный контракт до разработки
    Во-первых, вы можете спастись от многих ошибок, выявив их на этапе проектирования. Во-вторых, у вас, помимо классно работающего API, появляется документация, которая даст пользователям понимание, как пользоваться вашим API.

  • Параллельная разработка
    Спроектировав заранее контракт API, команды тестирования, разработки клиента API и разработки сервиса, реализующего API, могут работать параллельно, что, в свою очередь, приводит к увеличению производительности команд.

  • Скорость и качество тестирования
    Спецификация API упрощает QA-специалистам создание тест-кейсов, что обеспечивает общее более высокое качество ПО. Также на основе спецификации OpenAPI можно генерировать API-тесты.

  • Кодогенерация
    Из готовой спецификации OpenAPI можно сгенерировать клиент, сервер, документацию (HTML, Confluence Wiki), ну и, как было сказано выше, — API-тесты, что позволит вам автоматизировать большую работу в рамках всего проекта и уменьшить объём рутинного кода.

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

    Сразу хочу всех успокоить: разработчиков из данного этапа никто не исключает, они неотъемлемая часть и также играют важную роль. Ну и частенько именно разработчики полностью берут на себя проектирование API, просто, скорее всего, вы в такой спецификации не увидите описания параметров и корректных понятных примеров. Но в любом случае нужно решить ряд вопросов совместно:

    • как будут взаимодействовать аналитик, архитектор и разработчик при проектировании спецификации;

    • процесс ревью спецификации;

    • как вносить изменения в спецификацию;

    • как уведомлять о готовности спецификации;

    • где хранить спецификации и куда доставлять изменения;

    • поддержка моков и многое другое.

Design API First-подход в первую очередь помогает создавать API более эффективно, в связи с чем все чаще встречается именно ручная разработка спецификации. Т.к. спецификация может быть максимально полезна ещё до разработки кода — согласованные контракты с примерами, моки, тесты и т. д.

Спецификация OpenAPI

Все мы хорошо знаем, что REST — это архитектурный стиль, а не конкретный стандарт. Тем не менее есть несколько спецификаций REST, которые помогают представить стандарты в виде описания REST API.

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

Наиболее используемые инструменты Swagger:

  • Swagger UI — веб-приложение, которое позволяет визуализировать спецификацию Open API в интерактивном пользовательском интерфейсе, выполнить запрос на сервер и получить ответ;

  • Swagger Codegen — генерация клиентов, серверных заглушек, SDK-пакетов и документации на основе определений спецификации Open API;

  • Swagger Core — генерация документации на основе аннотаций в существующем коде.

Нас больше всего сейчас интересует, что использовать для разработки спецификации OpenAPI, а это:

  • Swagger editor — интерфейс для создания файла документации по спецификации Open API. Не очень удобен тем, что большой файл будет грузиться достаточно долго...

  • Любая IDE с расширением для валидации и визуализации OpenAPI-спецификации (Swagger Viewer и др.).

Конечно, можно использовать и обычный текстовый редактор, но использование инструментов, которые позволяют визуализировать вашу спецификацию, упростят проверку её валидности.

Спецификацию OpenAPI мы можем писать как на JSON, так и на YAML.

С YAML работать легче, потому что не требуется расставлять скобки и запятые, т.е. документ более понятный для человека, но есть нюанс — надо внимательнее следить за расстановкой интервалов.

Далее будем рассматривать структуру спецификации OpenAPI в обоих вариантах.

Организация файлов спецификации OpenAPI

  • Для того чтобы спецификацию можно было легко и приятно поддерживать, да и в целом читать, я обычно выношу описание методов и описание моделей в отдельные файлы и ссылаюсь на нужные существующие объекты с помощью указания путей до них. Можно отдельно вынести и описание параметров, описание ошибок — но это поможет, если ваши сервисы содержат очень много повторяющихся параметров и/или ошибок.

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

  • Относитесь к спецификации как к программному коду. Иначе в конечном итоге она превратится в мусор.

Для себя я выделила следующий паттерн в организации файлов спецификации OpenAPI:

  • root_folder ← корень репозитория

    • resource_or_api_controller ← папка ресурса (если делим не по тегам в одном файле спецификации, а разносим по отдельным файлам)

      • api.yaml← файл для описания методов

      • models.yaml← файл для описания моделей

      • parameters.yaml← файл для описания параметров api-методов (опционально) 

      • errors.yaml← файл для описания ошибок, которые могут вернуть методы (опционально) 

Конечно, можно всю спецификацию описать в одном файле, но если у вас большой сервис со множеством методов и большими моделями, то и спецификация будет соответствующая. Навигироваться по такому файлу будет сильно сложнее. 

Базовая структура спецификации OpenAPI

Из чего состоит спецификация OpenAPI:

  • openapi - содержит номер версии спецификации OpenAPI. 

  • info - cодержит основную информацию о вашем API: название, описание, версию, контакты разработчика спецификации и т. д.

  • servers - содержит информацию об используемых серверах.

    • указывается базовый путь, который находится перед конечной точкой.

    • можно указать несколько вариантов - для разных сред разработки

  • components - в объекте components можно хранить множество различных переиспользуемых объектов. Объект components может содержать следующее: схемы, ответы, парамет��ы, примеры, тело запроса, заголовки, схемы безопасности и тд. При делении спецификации на два файла — api.yaml и models.yaml — нам данный блок нужен только для определения схемы безопасности, всё остальное уходит в файл models.yaml.

  • security - для отправки запросов, авторизованных нашим сервером API, спецификация должна содержать информацию о безопасности, которая авторизует запрос. 

    • объявленные поля components.securitySchemes и security свидетельствуют о том, что у любого метода в этом файле должен быть установлен хедер Authorization, в нашем случае с JWT-токеном.

    • при этом у каждого метода можно определить секцию security:[], оставив её пустой, которая будет свидетельствовать о том, что для данного метода авторизация не нужна.

    • о том, как декларировать различные схемы авторизации (apiKey, http, ouath2, openIdConnect, mutualTLS), можно в подробностях почитать в официальной документации OpenAPI

  • paths - содержит доступные пути (конечные точки) и операции (методы) для API. Подробнее о заполнении данного блока рассмотрим в следующем разделе.

  • tags - в данном объекте перечисляются все теги, в которые вы будете объединять свои конечные точки (по пользовательским ролям или фичам продукта).

  • externalDocs - содержит ссылки на внешние ресурсы для получения расширенной информации.

Пример заполнения в yaml
openapi: 3.1.0
info:
  title: "Myproject API"
  description: “Description of the purpose of your service”
  version: "1.0.0"
  termsOfService: “https://myproject.ru/terms”
  contact:
    name: Ванька Петров
    email: user@gmail.com
servers:
 - url: https://dev.api.myproject.ru/api/v2
   description: Test server dev
 - url: https://stg.myproject.ru/api/v2
   description: Test server stg
 - url: https://myproject.ru/api/v2
   description: Test server prod
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
paths:
  /users:
	get:
      security:
    	- bearerAuth: []
tags:
 - name: Profile
   description: "Профиль"
 - name: Products
   description: "Товары" 
externalDocs:
  description: Find more info here
  url: https://example.com

Пример заполнения в json
{
  "openapi": "3.1.0",
  "info": {
    "title": "Myproject API",
    "description": "Description of the purpose of your service",
    "version": "1.0.0",
    "termsOfService": "https://myproject.ru/terms",
    "contact":{
  	    "name": "Ванька Петров",
       	"email": "user@gmail.com"
    	}
  },
  "servers": [
	{
    	"url": "https://dev.api.myproject.ru/api/v2",
        "description":"Test server dev"
	},
	{
    	"url": "https://stg.myproject.ru/api/v2",
        "description":"Test server stg"
	},
	{
    	"url": "https://myproject.ru/api/v2",
        "description":"Test server prod"
	}
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "jwt"
      }
    }
  },
  "paths": {
    "/users": {
      "get": {
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    }
  },
  "tags": [
   {
     "name": "Profile",
     "description": "Профиль"
   },
   {
     "name": "Products",
     "description": "Товары"
   }
  ],
  "externalDocs":{
   "description": "Find more info here",
   "url": "https://example.com"
  }
}

Далее чуть подробнее разберёмся с ресурсами и методами. Для общего понимания — как может выглядеть ваша спецификация на один метод (в упрощённом варианте описания):

api.yaml
info:
  title: API Test
  version: '1.0'
servers:
  - url: https://api.server.test/v1
paths:
  /feedback/{id}/report:
	post:
     summary: Название метода
  	description: Описание работы метода
      security:
    	- bearerAuth: []
  	parameters:
    	- in: path
      	name: id
      	required: true
      	schema:
        	type: string
  	requestBody:
    	content:
      	application/json:
        	schema:
          	$ref: '#/components/schemas/Report'
    	required: true
  	responses:
 	   '200':
      	description: OK
      	content:
        	application/json:
          	schema:
            	$ref: "#/components/schemas/InfoMessage"
components:
  securitySchemes:
  	bearerAuth:
    	type: http
    	scheme: bearer
       bearerFormat: JWT
  schemas:
	Report:
  	type: object
  	required:
    	- id
    	- name
  	properties:
    	id:
      	type: integer
      	format: int64
    	name:
      	type: string
	InfoMessage:
  	type: object
  	required:
    	- code
    	- message
  	properties:
    	code:
      	type: integer
      	format: int32
    	message:
      	type: string         

Ресурсы и методы

Объект path содержит доступные пути (конечные точки) и операции (методы) для API. Он состоит из:

  • пути (конечной точки) — все пути в блоке path задаются относительно URL, определённых в блоке «Серверы», то есть полный URL запроса будет выглядеть так: <server-url>/path.

  • операций (методов GET, POST и т. д.), которые в свою очередь включают:

    • summary — название метода

    • description — описание работы метода. Описывайте задачу, которую решает метод или свойство

    • security: [] — определяет глобальный метод безопасности

    • parameters — параметры запроса

    • requestBody — тело запроса

    • responses — описание ответа

Есть множество других элементов, но остановимся на обязательной основе.  

Правила именования пути

Есть 3 типа ресурсов:

  • Документ — один объект. К примеру, одно сообщение в списке (api/messages/{id} — документ обычно вложен в коллекцию, но есть исключения).

    • в пути используются в таком случае только существительные.

    • последнее существительное в единственном числе.

  • Коллекция — множество объектов. К примеру, список сообщений (api/messages).

    • в пути используются в таком случае только существительные.

    • последнее существительное во множественном числе.

  • Контроллер — действие. К примеру, оформление заказа (/api/cart/checkout).

    • можно использовать глаголы.

    • действие должно относится к чему-то (/api/cart/checkout — checkout относится к корзине, лучше не делать просто /api/checkout — не самый лучший пример, но суть должна быть понятна).

Если вы следуете принципам REST, то в названии пути не пишем действие, о котором говорит HTTP method (create, update, delete и т. д.). Стараемся делать как можно больше документов и коллекций и как можно меньше контроллеров.

POST /courses — Создать новый курс

POST /courses/create — Создать новый курс 

Но здесь надо помнить, что ваш API может быть реализован полностью на POST, и в таком случае вполне нормально использовать действия в названии пути:

POST /courses/create — Создать новый курс

Методы

Метод

Описание

Комментарий

GET

Возвращает представление ресурса по указанному универсальному коду ресурса (URI). Текст ответного сообщения содержит сведения о запрашиваемом ресурсе.

GET-запрос может содержать тело запроса, но прокси могут просто отбрасывать тело GET-запроса.

GET-запросы по умолчанию кешируются через URI. Если параметры передаются в теле, то кеши работать не будут.

ОС может самостоятельно повторить GET-запрос.

POST

Создаёт новый ресурс по указанному URI. Текст запроса содержит сведения о новом ресурсе.  

Метод POST также можно использовать для запуска операций, не относящихся непосредственно к созданию ресурсов (для операции контроллера).

PUT

Создаёт или заменяет ресурсы по указанному URI.  

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

Лучше все-таки разделять создание (делать POST) и изменение (PUT / PATCH).

Полностью перезаписывает ресурс.

PATCH

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

Перезаписывает только определённую часть.

 

DELETE

Удаляет ресурс по указанному URI.

Не содержит тела.

Параметры пути и запроса

Параметры пути и запроса состоят из:

  • name: имя параметра.

  • in: место параметра. Возможные значения:

    • header — параметры, включённые в заголовок запроса

    • path — параметры в пределах path конечной точки перед строкой запроса

    • query — параметры в строке запроса конечной точки

    • cookie — параметры в заголовке Cookie

  • description: описание параметра.

  • required: обязательность параметра.

  • schema: схема или модель для параметра. Схема определяет структуру входных или выходных данных.

  • example: пример типа носителя. Если объект example содержит примеры, эти примеры появляются в Swagger UI, а не в содержимом объекта example.

Также параметры запроса можно выносить в models.yaml и ссылаться ($ref) на параметры из моделей. Пример:

parameters:

  - $ref: "models.yaml#/components/parameters/Param1"

Для добавления в компоненты параметров необходимо на уровне с элементом schema добавить элемент parameters и описать там все необходимые параметры.

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

Описание параметра запроса в models.yaml для того, чтобы ссылаться на него и переиспользовать без дублирующего описания.

parameters:
  filter_type:
   name: filter_type
   in: query
   description: |
 	Тип фильтра заказов пользователя:
 	- all — все заказы
 	- current — текущие
 	- done — выполненные
   schema:
 	enum: ["all", "current", "done"]
  	type: string

Параметры пути

Для того чтобы добавить параметр в путь запроса, необходимо использовать фигурный скобки {}. Обычно это используется для указания определённого элемента в коллекции. Путь может иметь несколько параметров

GET /users/{userId}:

GET /cars/{carId}/drivers/{driverId}:

Каждый параметр пути должен быть заменён фактическим значением при вызове.

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

paths:
  /users/{userId}: 
    get:
      parameters:
	    - name: userId   # имя использовать такое же, как и в пути
  	      in: path
  	      description: Идентификатор пользователя
  	      required: true # обязательный параметр
  	      schema:
            type: integer
            format: int64

Параметры запроса

Параметры запроса отображаются в конце URL-адреса после знака вопроса (?). Несколько значений должны разделяться амперсандом (&).

GET /pets/findByStatus?status=available

GET /notes?offset=100&limit=50

Для определения таких параметров нужно использовать следующую конструкцию in: query.

paths:
  /notes:
    get:
      parameters:
	    - name: offset
  	      in: query
  	      description: The number of items to skip before starting to collect the result se
  	      schema:
    	    type: integer
	    - name: limit
  	      in: query
  	      description: The numbers of items to return
  	      schema:
     	    type: integer
Примеры оформления параметров запроса.
Объявление параметра запроса
Объект в качестве параметра запроса (поддерживается с OpenAPI 3)
Ссылка на модель с параметрами

Про параметры заголовка и куки подробнее можно прочитать в соответствующих разделах официальной документации OpenAPI.

Тело запроса

POST-, PUT- и PATCH-запросы могут иметь тело запроса.

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

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

Пример:

requestBody:
  required: true
  content:
   	application/json:
     	schema:
         	$ref: "models.yaml#/components/schemas/RecalculateOrderRequest"
Примеры оформления тела запроса.
Объявление тела запроса
Объявление тела запроса ссылкой на модель

Ответ

Описание REST-запроса обязательно должно содержать описание ответа (responses). Response задаётся HTTP-кодом ответа и данными, которые возвращаются в теле ответа и/или заголовке.

Описание ответа начинается с кода, такого как 200 или любого другого. Методы обычно возвращают один успешный код и один и более кодов ошибок. Каждый код требует описания (description) — условие, при котором код срабатывает. Если вы не можете задать определённый код, то его можно задать следующим видом: 1XX, 2XX, 3XX, 4XX, 5XX. Но таким образом в случае, если был задан код 404 и 4XX, приоритет у первого будет выше.

responses:
	'201':
  	description: Бонусы успешно списаны.
     '400':
  	description: Неверный запрос
  	content:
    	application/json:
      	schema:
        	$ref: "../common/models.yaml#/components/schemas/ResponseBadParameters"
   '401':
  	description: Несанкционированный запрос
  	content:
    	application/json:
      	schema:
        	$ref: "../common/models.yaml#/components/schemas/ResponseUnauthorized"
    	'500':
  	description: |
    	Возможные ошибки
    	* `101` — UserBlocked, пользователь был заблокирован
    	* `104` — OTPCodeInvalid, неверный OTP-код
  	content:
    	application/json:
      	schema:
         	$ref: "../common/models.yaml#/components/schemas/ErrorResponse"
	'426':
   	description: Необходимо обновить приложение

Для передачи файлов в запросе или ответе в OpenAPI 3.0 используется type: string и format: binary или format: base64.

paths:
  /report: 
    get:
      summary: Returns the report in the PDF format
      responses:
	    '200':
  	      description: A PDF file
  	      content:
    	    application/pdf:
      	      schema:
        	    type: string
         	    format: binary
Примеры оформления ответа.
Объявление ответа ссылками на модели
Объявление ответа ссылками на модели
Обычный текстовый ответ с заголовками
Обычный текстовый ответ с заголовками

Статус-коды (основные)

Код

Описание

Часто используемые коды

2xx

Операция завершилась успешно

200 OK — ответ на успешные GET, PUT, PATCH, DELETE, а также для POST, который не привёл к созданию.

201 Created — используется в методах POST и имеет тело ответа, чтобы сказат�� клиенту, что мы создали в итоге — как минимум получить идентификатор записи.

202 Accepted — указывает на то, что запрос принят к обработке (обработка ещё не завершена или даже не начата), и клиенту необязательно ждать завершения операции.

204 No Content — операция прошла успешно, но тело ответа не требуется (например, запрос DELETE). 

3xx

Редирект или можем пойти читать из кэша

304 Not Modified — свидетельствует о том, что данные не изменились и можно читать данные из кэша. Обычно работает с E-Tag или Cache-Control-заголовками.

4xx

Операция завершилась с ошибкой по вине клиента

Из тех, что стоит фиксировать в спецификации:

400 Bad Request — сервер не смог понять запрос из-за недействительного синтаксиса.

401 Unauthorized — пользователь не авторизован для доступа.

403 Forbidden — пользователь не имеет права на запрашиваемый ресурс.

404 Not found — запрашивается несуществующий ресурс.

426 Upgrade Required — указывает на то, что сервер отказывается выполнять запрос с использованием текущего протокола, но может захотеть сделать это после того, как клиент обновится до другого протокола (используется, когда версия приложения уже не поддерживается и пользователю предлагается обновить приложение при получении данной ошибки).

429 Too Many Requests — запрос отклоняется из-за ограничения скорости (слишком много запросов за определённый промежуток времени).

5xx

Операция завершилась с ошибкой по вине сервера (или не смог сразу определить, что по вине клиента)

Конкретные 5xx ошибки не фиксируем обычно в спецификации API, но если необходима необычная обработка, то фиксируйте (к примеру, нужна определённая заглушка на ошибку временной неработоспособности сервера — 503 Service Unavailable).

Структура и проектирование моделей

В файл models.yaml я обычно выношу components, который в свою очередь включает:

  • schemas — модели,

  • parameters — параметры.

Пример структуры файла:

components:
   schemas:
     UpdatedOrderResponse:
       type: object
       description: Модель для обновлённых полей заказа после выполнения действия над ним.
       properties:
         status:
           $ref: "#/components/schemas/ExtendedOrderStatus"
         actions:
           type: array
           description: |
             Список действий, доступных над заказом.
             Список пуст, если нет доступных действий.
           items:
              $ref: "#/components/schemas/OrderAction"
       required:
         - status
         - actions
     ReceiverType: # модель, которая содержит ограничения возможных значений по типу плательщика
       type: string
       enum: [individual, entity]
       description: |
         Тип плательщика:
         - individual — физическое лицо
         - entity — юридическое лицо
 
   parameters: # параметры, которые можно переиспользовать в параметрах запроса
     filter_type: 
       name: filter_type 
       in: query
       description: | 
         Тип фильтра заказов пользователя: 
         - all — все заказы
         - current — текущие
         - done — выполненные
       schema:
         enum: ["all", "current", "done"] 
          type: string

Комментарий (description) и пример (example) — очень важная часть спецификации. Применимо как к методам, так и к моделям.
Уделяйте большое внимание этим полям и описывайте максимально понятно, вкладывайте контекст, логику, примеры — пишем как можно больше (в пределах разумного, конечно, описывать суперподробно user.name не стоит).

Типы параметров

С помощью ключевого слова type задаётся тип данных. Типы могут быть следующими (основные):

  • string — строка текста.

  • number — включает в себя и целые числа, и числа с плавающей точкой.

  • integer — только целые числа.

  • boolean — в логическом типе boolean представлено два возможных значения: true и false.

  • array — массив.

  • object — объекты — коллекция пар «ключ и значение».

Строка

  • Длину строки можно ограничить, используя для этого minLength и maxLength.

  • Ключевое слово pattern позволяет определить шаблон регулярного выражения для строки — значения, которые могут быть использованы в строке. Для задания pattern используется синтаксис регулярного выражения из JavaScript (pattern: '^\d{3}-\d{2}-\d{4}')." — конца строки. Без ^… $ шаблон соответствует любой строке, содержащей указанное регулярное выражение.

  • Ключевое слово format используется для того, чтобы задать формат строки, например, один из них: date (2017-07-21), date-time (2017-07-21T17:32:28Z), password, byte, binary

К примеру, дл�� передачи файла используется:

avatar:          # изображение, встроенное в JSON
 description: Base64-encoded contents of the avatar 
 type: string
  format: byte

Числа

  • Для указания диапазона возможных значений можно использовать ключевые слова minimum и maximum (minimum ≤value≤ maximum).

  • Чтобы исключить граничные значения, укажите exclusiveMinimum: true и exclusiveMaximum: true.

count:      	
  description: Суммарное количество товаров в заказе 
  type: integer
  example: 6
  maximum: 25
id:
  description: Идентификатор пользователя
  type: integer
  format: int64

Массивы

  • С помощью minItems и maxItems можно задавать минимальную и максимальную длины массива. Если не использовать minItems, то массив может быть пустым.

  • Элементы массива описываем отдельной моделью, если они представляют собой коллекцию.

# Элементы массива отдельной моделью
actions:         
  description: |
    Список действий, доступных над заказом. 
    Список пуст, если нет доступных действий. 
  type: array
  items:
    $ref: "#/components/schemas/OrderAction"
# Массив строк
categories:
  description: |
    id категорий товаров первого уровня, в которые входят товары данной акции 
  type: array
  items:
    type: string

Объекты

  • По умолчанию все элементы коллекции необязательные. Можно указать список обязательных элементов с помощью слова required.

  • Коллекция также может быть вложенной и включать в себя коллекцию. В таком случае коллекцию оформляем отдельным объектом и даём на него ссылку для удобства всех членов команды.

Проверка валидности спецификации OpenAPI

Минимальная проверка спецификации API может быть провед��на путём визуализации спецификации OpenAPI. Если вы делили спецификацию на разные файлы для методов и моделей, то визуализацию надо запускать, находясь в файле с методами.

Надо проверить, что спецификация визуализируется, все параметры отрендерены, прописаны обязательные и nullable-поля. Запросы и ответы также отрендерены, и не отображаются ошибки.

Если спецификация не рендерится, на что обратить внимание:

  • интервалы. Проверьте, что все объекты находятся на своём уровне;

  • ссылки. Проверьте, что все ссылки корректны и они ссылаются на существующие модели.

Также можно настроить линтер, который, например, будет запускаться каждый раз после того, как вы запушите свою спецификацию в ветку.

Дополнительная информация для ознакомления

Headers

Основные заголовки:

  • Accept-Charset — способ клиента сказать, в какой кодировке нужны данные (UTF-8, ASCII, whatever). Обычно всегда используется UTF-8, и менять не нужно.

  • Accept-Encoding (аналог с сервера — Content-Encoding) — то, как данные от сервера закодированы, обычно речь про алгоритм сжатия. Например, gzip.

  • Accept-Language (аналог с сервера — Content-Language) — то, какой язык хочет получить клиент. Использовать можно для мультиязычных сервисов.

  • Accept (аналог с сервера — Content-Type) — форматы данных, которые клиент поддерживает, эти форматы называются MIME-типами. Например, application/json. Такое часто бывает при передаче файлов или когда хотим открыть файл в вебе, здесь нужно правильно установить MIME-тип.

  • Cookie — это способ хранить состояние. Как это работает:

    • сначала сервер просит клиента установить cookies (Set-Cookie);

    • клиент при обращении отправляет их серверу в заголовке с ключом Cookie.

Если вы используете Cookie для передачи токена, то в таком случае обязательны параметры:

  • secure=true

  • httponly=true

  • samesite=strict

Общие правила при проектировании спецификации OpenAPI, которые были выработаны совместно с командой

Правило

Описание

Использовать kebab-case для URL

Мы используем: /set-unread

В других командах могут делать и так: /setUnread или /set_unread

Использовать camelCase для параметров пути

Мы используем: /orders/{orderId}

В других командах могут делать и так: /orders/{order_id} или /orders/{OrderId}

Использовать множественное число для коллекций

Мы используем: GET /users

В других командах могут делать и так: GET /user или GET /User

Не использовать глаголы в URL ресурсов

Вместо этого используем HTTP-методы для описания операций.

Мы используем: POST /courses/{courseId} или GET /courses

В других командах могут делать и так: POST /updatecourse/{courseId} или GET /getcourses

Использовать snake_case для JSON-свойств

Мы используем:

{

  "user_name": "Ванька Петров",

  "user_id": "1"

}

В других командах могут делать и так:

{

  "userName": "Ванька Петров",

  "userId": "1"

}

Не используем kebab-case:

{

  "user-name": "Ванька Петров",

  "user-id": "1"

}

Использовать глаголы в URL операций

Для функций, которые выполняют определённые действия на сервере и при этом не являются CRUD-операцией:

Мы используем: POST /messages/{messageId}/resend

Использовать простой порядковый номер для версий

Если поддерживаем версионирование API, то используем простой порядковый номер и всегда указываем его на самом верхнем уровне.

http://api.domain.com/v1/shops/3/products

Указывать количество элементов в ответе на запрос

Если есть возможность возвращать общее количество элементов и это не скажется плохо на вашей производительности — возвращайте.

{

 "users": [

     ...

 ],

  "offset": 0,

 "total": 34

}

Не передавать аутентификационные токены в URL

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

Мы используем: заголовки авторизации и Cookies.

Исключения: GET /resourse?token=authenticaiton_token 

Использовать HTTP-методы для CRUD-операций

В этом и есть их смысл.

Опять же — если вы следуете принципам REST.

Заключение

В этой статье мы познакомились со структурой спецификации OpenAPI и примерами её использования.

Ещё раз напомню, что REST — это архитектурный стиль, а не стандарт. Поэтому всё сказанное в этой статье основано на реальном опыте системных аналитиков одной из ИТ-компаний нашей необъятной страны и не является призывом к обязательному применению.

В разных компаниях при использовании разных языков программирования или просто, потому что так «исторически сложилось», могут прибегать к тем или иным решениям, о которых мы в этой статье не поговорили или они не соответствуют описанному. Если статья для вас не имеет никакого смысла, буду рада услышать ваши комментарии о том, какие практики проектирования API используете вы.

В этой статье будет только одна РЕКОМЕНДАЦИЯ: придерживайтесь того, что принято в вашей компании и команде, а данная статья может стать неплохой основой для формирования корпоративного гайдлайна, если у вас такового нет 🙂

Всем добра!