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

от автора

Привет, Хабр! Меня зовут Виктория Юльская, и я старший системный аналитик в 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.yam← файл для описания методов

      • 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 используете вы.

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

Всем добра!


ссылка на оригинал статьи https://habr.com/ru/articles/825008/