https://github.com/surfstudio/api-design-best-practices
https://github.com/surfstudio/api-design-best-practices
Last synced: 6 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/surfstudio/api-design-best-practices
- Owner: surfstudio
- License: mit
- Created: 2021-06-30T05:10:28.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2021-08-30T07:37:19.000Z (about 4 years ago)
- Last Synced: 2025-04-13T07:13:31.666Z (6 months ago)
- Language: Shell
- Size: 1.34 MB
- Stars: 8
- Watchers: 4
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Лучшии практики ведения API спецификации (OpenAPI) в Surf
В этом репозитории находится пример спецификации (папка example) и описаны правила работы с ней, а также распространенные кейсы (файл cases).# Содержание
- [Лучшии практики ведения API спецификации (OpenAPI) в Surf](#лучшии-практики-ведения-api-спецификации-openapi-в-surf)
- [Содержание](#содержание)
- [Общие принципы](#общие-принципы)
- [Организация файлов и папок](#организация-файлов-и-папок)
- [Проектирование спецификации API](#проектирование-спецификации-api)
- [Инструменты](#инструменты)
- [Генерация папок для фичи](#генерация-папок-для-фичи)
- [Базовая структура](#базовая-структура)
- [Ресурсы и методы](#ресурсы-и-методы)
- [Правила именования пути](#правила-именования-пути)
- [Методы](#методы)
- [Параметры пути и запроса](#параметры-пути-и-запроса)
- [Параметры пути](#параметры-пути)
- [Параметры запроса](#параметры-запроса)
- [Тело запроса](#тело-запроса)
- [Ответ](#ответ)
- [Структура и проектирование моделей](#структура-и-проектирование-моделей)
- [Типы параметров](#типы-параметров)
- [Ошибки, хэширование, кэширование и тд](#ошибки-хэширование-кэширование-и-тд)
- [Быстрые и полезные комбинации](#быстрые-и-полезные-комбинации)
- [Проверка спецификации API на фичу](#проверка-спецификации-api-на-фичу)
- [Проверка всей спецификации API через линтер SurfGen](#проверка-всей-спецификации-api-через-линтер-surfgen)
- [Дополнительная информация для ознакомления и советы](#дополнительная-информация-для-ознакомления-и-советы)
- [Хэдеры](#хэдеры)
- [Советы](#советы)---
# Общие принципы- Спецификация должна быть побита на разные файлы. Это **необходимо** для того, чтобы ее можно было легко и приятно поддерживать (дополнять, изменять) и вообще читать.
- Корневой README.md должен содержать информацию о том, как работать с источниками в репозитории. Потому что кто-то может быть не в курсе.
- Хорошо иметь скрипт для автогенерации шаблона. Чтоб не копипастить каждый раз самим.
- Старайтесь использовать текстовый формат (не используйте GUI), потому что так проще навигировать. И так можно пользоваться поиском.
- Относитесь к спецификации как к программному коду. Иначе в конечном итоге она превратится в мусор.# Организация файлов и папок
Хорошо придерживаться следующего паттерна:
```
- root_folder <- корень вашего репозитория
-- README.md
-- feature_or_api_controller <- папка, имеющая название фичи
--- api.yaml <- Файл исключительно для описание сервисов (path в Swagger)
--- models.yaml <- Файл исключительно для описания моделей (components/schemas в Swagger)
--- parameters.yaml <- Файл исключительно для описания параметров api-методов (components/parameters в Swagger)
--- errors.yaml <- Файл исключительно для описания ошибок которые могут вернуть методы (components/schemas в Swagger)
```**ВАЖНО**
- `api.yaml` и `models.yaml` обязательны! Потому что в противном случае люди будут бояться лезть менять ваши файлы.
- `parameters.yaml` и `errors.yaml` не обязательны! Они могут быть использованы в тех случаях, когда сервисы содержат очень много повторяющихся параметров и/или ошибок.
- Не стоит выносить отдельно components/requestBodies , потому что тогда, чтобы добраться от метода до модели, потребуется 2 перехода. Это уже неудобно.# Проектирование спецификации API
Перед проектированием спецификации API подразумевается, что вы:
- Клонировали репозиторий и наполнили его файлами для генерации и readme.
- Создали ветку фичи, которую будете проектировать.
- Установили необходимые расширения в VSCode.# Инструменты
- [VSCode](https://code.visualstudio.com/download ) скачать и установить, если еще не установлен.
- Расширения для VSCode - для установки переходим в раздел Расширения в левом боковом меню и ищем по названию необходимое расширение:
- [Swagger Viewer](https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer) - Предварительный просмотр спецификации API, происходит в режиме реального времени по мере ввода.
- [GitHub Pull Requests and Issues](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) (опционально) - можно смотреть и комментировать PR-ы прямо из VSCode. Необходимо авторизоваться через VSCode в GitHub, появится иконка GitHub-а в боковом меню, где можно просматривать и комментировать PR-ы.
- [Git Blame](https://marketplace.visualstudio.com/items?itemName=waderyan.gitblame) (опционально). Расширение Git Blame предоставляет возможность просматривать информацию состояния для текущей выбранной строки. Оно позволяет выяснить, кто писал определенные фрагменты кода.
- [Git History](https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory) (опционально). Расширение Git History предоставляет возможность тщательно изучить историю файла, автора, ветки. Чтобы активировать окно Git History, нужно кликнуть на файл правой кнопкой мыши и выбрать Git: View File History. Кроме того, вы можете сравнивать ветки и коммиты, создавать ветки из коммитов и многое другое.# Генерация папок для фичи
Хорошо иметь какой-то инструмент, который позволит сгенерить нужные файлы с предзаполненной мета информацией - чтоб люди не писали по 100 раз одно и то же.В этом репозитории это сделано с помощью шелл-скрипта [gen.sh](./gen.sh) и двух файлов с шаблонами [.api_template](./.api_template) и [.models_template](./.models_template)
Сам скрипт позволяет сгенерировать "контроллер" с отдельным файлом для моделек и отдельным файлом для методов API.
Т.к. мы проектируем уже фичу, то все махинации должны производиться в соответствующей ветке.Пошаговая инструкция генерации папки для фичи:
| № | Шаг |
| ------------- |:------------------|
| 1 | Проверяем, что мы в правильной ветке. Если работаем над авторизацией, то в ветке auth, которую мы создали. |
| 2 | Открываем консоль, перемещаемся в корень репозитория:
1. Идем в папку где располагается репозиторий (к примеру, `E:\work\my-project-swagger`).
2. Shift+клик по правой кнопка мыши → “Открыть окно PowerShell здесь”, на macOS соответственно терминал открываем или же через терминал VSCode. |
| 3 | Вводим в консоль `./gen.sh ./{название фичи (оно же название папки)}`. Пример: `./gen.sh ./auth` или `sh gen.sh catalog` |
| 4 | Скрипт создаст папку `catalog` и добавит в нее два файла `api.yaml` и `models.yaml` с уже заполненной шапкой. Если папка уже есть, то скрипт просто положит в нее два файла.
Всё, папки появились. Если нет, жмем кнопку обновления проводника (Explorer) в VSCode. Созданы два файла в папке `auth` с нужными шапками/шаблонами: api.yaml - для описания эндпоинтов, models.yaml - для описания моделей к ним|
| 5 | Можем начинать проектирование фичи, для которой создали папку. |# Базовая структура
Структура файла `api.yaml` или что нагенерил мне скрипт:> Все поля, кроме `path`, заполняются при генерации папок. Но понимать, что они означают будет полезно.
Название поля
Описание
openapi
Обязательное для заполнения.
Номер версии спецификации OpenAPI. Последняя версия на данный момент 3.0.2.
Это поле не связано со строкой API -info.version
.
info
Обязательное для заполнения.
Содержит основную информацию о вашем API: название (title
), описание вашего API (description
), версия вашего API (version
), контакты разработчика спецификации (contact.name
,contact.email
). В данной инструкции указан не весь набор элементов, которые могут содержаться в info-блоке, но для нашего использования этого достаточно.
Пример:
```yaml
info:
title: "API"
version: "1.0.0"
contact:
name: Юльская Виктория
email: yulskaya@surfstudio.ru
```
servers
Содержит информацию об используемых серверах: url и описание сервера (description). Их может быть несколько.
Пример:
```yaml
servers:
- url: https://dev3.myproject.ru/api/surf/v1/
description: Test server dev3
- url: https://surf.myproject.ru/api/surf/v1/
description: Test server surf
- url: https://myproject.ru/api/surf/v1/
description: Production Server
```
components
В объекте components можно хранить множество различных переиспользуемых объектов. Объект components может содержать следующее:
При делении папки на два файла api.yaml и models.yaml нам данный блок нужен только для определения схемы безопасности, все остальное уходит в файл models.yaml.
Если спецификация заказчика вся ведется в одном файле, то все вышеперечисленное будет в данном блоке.
Лучше сразу понять как будет проходить авторизация и дальнейшая проверка наличия прав пользователя при запросе на ресурс - через токены / куки / что-то другое (разработчикам надо закладывать это в самом начале, во время первого спринта, а то и во время инициализации).
Пример:
```yaml
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
```
security
Для отправки запросов, авторизованных нашим сервером API, спецификация должна содержать информацию о безопасности, которая авторизует запрос.
```yaml
security:
- bearerAuth: []
```
Объявленные поля components.securitySchemes и security свидетельствуют о том, что у любого метода в этом файле должен быть установлен хедер Authorization с JWT токеном.
При этом у каждого метода можно определить секцию security:[] , оставив ее пустой, которая будет свидетельствовать о том, что для данного метода авторизация не нужна.
paths
Обязательное для заполнения.
Содержит доступные пути (конечные точки) и операции (методы) для API.
Подробнее о заполнении данного блока рассмотрим чуть ниже.
# Ресурсы и методы
Блок path состоит из:
- пути (конечной точки) - все пути в блоке `path` задаются относительно URL, определенных в блоке "Серверы”, то есть полный 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`).
> Стараемся делать как можно больше документов и коллекций, и как можно меньше контроллеров
> В названии пути НЕ ПИШЕМ ДЕЙСТВИЕ, о котором говорит HTTP method!!! (create, update, delete и тд)
> - POST /courses - Создать новый курс
> - POST /courses/create - Создать новый курс
## Методы
| Метод | Описание | Комментарий|
| ------------- |:------------------|:------------------|
|`GET` |Возвращает представление ресурса по указанному универсальному коду ресурса (URI). Текст ответного сообщения содержит сведения о запрашиваемом ресурсе. |
|`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`) на параметры из моделей. Пример:
```yaml
parameters:
- $ref: "models.yaml#/components/parameters/Param1"
```
Для добавления в компоненты параметров необходимо на уровне с элементом `schema` добавить элемент parameters и описать там все необходимые параметры.
Для ограничения возможных значений параметра запроса необходимо использовать ключевое слово `enum`.
```yaml
# Описание параметра запроса в models.yaml для того, чтобы ссылаться на него и переиспользовать без дублирующего описания.
parameters:
filter_type:
name: filter_type
in: query
description: |
Тип фильтра заказов пользователя:
- all - все заказы
- current - текущие
- done - выполненные
schema:
enum: ["all", "current", "done"]
type: string
```
## Параметры пути
Для того чтобы добавить параметр в путь запроса необходимо использовать фигурный скобки `{}`. Обычно это используется для указания определенного элемента в коллекции. Путь может иметь несколько параметров:
```yaml
GET /users/{id}:
GET /cars/{carId}/drivers/{driverId}:
```
Каждый параметр пути должен быть заменен фактическим значением при вызове.
Для определения параметров пути нужно использовать следующую конструкцию `in: path`. Необходимо также добавить `required: true`, чтобы указать обязательность данного параметра.
```yaml
paths:
/users/{id}:
get:
parameters:
- name: id # имя можно использовать такое же как и в пути
in: path
description: Идентификатор пользователя
required: true # обязательный параметр
schema:
type: integer
minimum: 1
```
## Параметры запроса
Параметры запроса отображаются в конце URL-адреса после знака вопроса (`?`). Несколько значений должны разделяться амперсандом (`&`).
```
GET /pets/findByStatus?status=available
GET /notes?offset=100&limit=50
```
Для определения таких параметров нужно использовать следующую конструкцию in: query.
```yaml
paths:
/notes:
get:
parameters:
- name: offset
in: query
description: The number of items to skip before starting to collect the result set
schema:
type: integer
- name: limit
in: query
description: The numbers of items to return
schema:
type: integer
```
>Не декларируйте здесь объекты!!!
Примеры оформления параметров запроса
| Не надо так | Надо вот так | Так тоже можно|
| :------------------ |:------------------|:------------------|
|||Если все же необходим объект, то объявите его в моделях и ссылайтесь на данный объект

Можно вынести отдельно параметры для дальнейшего переиспользования и ссылаться на объявленные параметры
 |
Про параметры заголовка и куки подробнее можно прочитать в [соответствующих разделах](https://swagger.io/docs/specification/describing-parameters/#path-parameters).
## Тело запроса
POST, PUT и PATCH запросы могут иметь тело запроса.
> 1. Мы всегда должны ставить ссылки на модели!!! Не нужно засорять нашу спецификацию перечислением того, что должно быть в моделях. У нас и так огромные методы, а если еще модели писать, то будет много дублирования и иных проблем. Делаем ссылки для своего удобства и для удобства всей команды.
2. Есть исключения в виде массивов или групп, в таком случае мы прописываем массив и как тип элементов, которые там лежат мы используем ссылку на модель элемента.
Пример:
```yaml
requestBody:
required: true
content:
application/json:
schema:
$ref: "models.yaml#/components/schemas/RecalculateOrderRequest"
```
Примеры оформления тела запроса
| Плохо | Хорошо |
| :------------------ |:------------------|
|||
## Ответ
Описание REST-запроса обязательно должно содержать описание ответа (`responses`). У каждого метода должен быть определен хотя бы один ответ (успешный). Response задается HTTP-кодом ответа и данными, которые возвращаются в теле ответа и / или заголовке.
> 1. Мы всегда должны ставить ссылки на модели!!! Не нужно засорять нашу спецификацию перечислением того, что должно быть в моделях. У нас и так огромные методы, а если еще модели писать, то будет много дублирования и иных проблем. Делаем ссылки для своего удобства и для удобства всей команды.
2. Есть исключения в виде массивов или групп, в таком случае мы прописываем массив и как тип элементов, которые там лежат мы используем ссылку на модель элемента.
Описание ответа начинается с кода, такого как `200` или `404`. Методы обычно возвращают один успешный код и один и более кодов ошибок. Каждый код требуется описания (`description`) - условие, при которых код срабатывает. Если вы не можете задать определенный код, то его можно задать следующим видом: `1XX`, `2XX`, `3XX`, `4XX`, `5XX`. Но таким образом, в случае если был задан код `404` и `4XX`, приоритет у первого будет выше.
```yaml
responses:
'201':
description: Бонусы успешно списаны.
'500':
description: |
Возможные ошибки
* `101` - UserBlocked, пользователь был заблокирован
* `104` - OTPCodeInvalid, неверный OTP-код
content:
application/json:
schema:
$ref: "../common/models.yaml#/components/schemas/ErrorResponse"
'426':
description: Необходимо обновить приложение
```
>Не стоит описывать все возможные коды ответов, тем более что о некоторых из них можно и не знать в момент проектирования запроса. Важно при описании кодов покрыть случай успешного выполнения запроса и коды ошибок, известных на момент написания REST-запроса.
Также, в Surf обычно описываются кастомные ошибки и заворачиваются в `400` или `500` статус код.
Кастомные ошибки описываются в отдельной папке common - где в `api.yaml` описывается справочник ошибок, а в `models.yaml` описывается модель ошибки, к примеру, `ErrorResponse`, который состоит из кода кастомной ошибки, сообщения в человекочитаемом виде, и при необходимости вспомогательная информация об ошибке
Для передачи файлов в запросе или ответе в OpenAPI 3.0 используется type: string и format: binary или format: base64.
```yaml
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 |Операция завершилась успешно|
Всегда содержит тело ответа. Может использоваться в GET запросах.
Используется в методах POST и имеет тело ответа, чтобы сказать клиенту, что мы создали в итоге - как минимум получить идентификатор записи).
Не содержит тело ответа.
Говорит о том, что клиенту не обязательно ждать завершения операции (но она еще не завершилась). Пример, оплата.
Если знаем что мы не ждем ответа, то ставим всегда данный код.|
|3xx |Редирект или можем пойти читать из кэша|
Можно читать данные из кеша. Обычно работает с E-Tag или Cache-Control заголовками.
**Работает только с GET запросами**|
|4xx |Операция завершилась с ошибкой по вине клиента|Из тех, что стоит фиксировать в спецификации:
Также, на `400` или `409` можно повесить кастомные ошибки и описать их в справочнике|
|5xx |Операция завершилась с ошибкой по вине сервера (или не смог сразу определить что по вине клиента)|Конкретные 5xx ошибки не фиксируем обычно в спецификации API, но если необходима необычная обработка, то фиксируйте (к примеру определенная заглушка на ошибку временной неработоспособности сервера - `503`)|
# Структура и проектирование моделей
Файл models.yaml состоит из
- `components`, который в свою очередь включает:
- `schemas` - модели
- `parameters` - параметры
Пример структуры файла:
```yaml
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`) очень важная часть спецификации. Применимо как к методам, так и к моделям.
Уделяйте большое внимание этому полю и описывайте как можно более понятнее, вкладывайте контекст, логику, примеры - пишем как можно больше (в пределах разумного, конечно, описывать супер подробно `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`
К примеру, для передачи файла используется:
```yaml
avatar: # изображение, встроенное в JSON
description: Base64-encoded contents of the avatar
type: string
format: byte
```
**Числа**
- Чтобы указать диапазон возможных значений, можно использовать ключевая слова `minimum` и `maximum` (minimum ≤value≤ maximum).
- Чтобы исключить граничные значения, укажите `exclusiveMinimum: true` и `exclusiveMaximum: true`
```yaml
count:
description: Суммарное количество товаров в заказе
type: integer
example: 6
maximum: 25
```
**Массивы**
- С помощью `minItems` и `maxItems` можно задавать минимальную и максимальную длину массива. Если не использовать `minItems`, то массив может быть пустым.
- Элементы массива описываем отдельным элементом, если они представляют собой коллекцию.
```yaml
# Элементы массива отдельным элементом
actions:
description: |
Список действий, доступных над заказом.
Список пуст, если нет доступных действий.
type: array
items:
$ref: "#/components/schemas/OrderAction"
# Массив строк
categories:
description: |
id категорий товаров первого уровня, в которые входят товары данной акции
type: array
items:
type: string
```
**Объекты**
- По умолчанию все элементы коллекции необязательные. Можно указать список обязательных элементов с помощью слова `required` (можно отказаться от `required` и прийти к тому, чтобы явно прописывать параметрам `nullable: false`, если поле не может быть пустым).
- Коллекция также может быть вложенной и включать в себя коллекцию. В таком случае коллекцию оформляем отдельным объектом и даем на него ссылку для удобства всех членов команды.

## Ошибки, хэширование, кэширование и тд
Для описания данной информации следует создать папку common, где фиксировать общие договоренности, которые в самих методах никак не отразить. Также, можно выносить общие методы, к примеру, загрузка изображения (профиля, отзыва и тд).
Пример оформления файла common/api.yaml
```yaml
openapi: 3.0.2
info:
title: "API"
version: "1.0.0"
contact:
name: Виктория Юльская
email: yulskaya@surfstudio.ru
description: |
# Headers
Для определения текущего пользователя, во всех запросах может как приходить, так и передаваться кастомный хедер `UserID`.
# Авторизация
Схема авторизации онована на механизме access/refresh токенов.
При логине - сервер возвращает пару access/refresh токен.
В дальнейшей работе с сервером во всех запросах необходимо передавать в хедере `Authorization`
полученный access токен.
При его протухании - необходимо произвести обновление токенов с помощью соответствующего метода,
используя refresh токен.
При получении ошибки во время обновления токенов - необходимо закончить текущую сесию работы с сервером
и разлогинить пользователя.
# Пагинация
Проект поддерживает страничную пагинацию.
Каждый пагинируемый запрос должен url-параметрами принимать `page` и `size`,
а в теле ответа возвращать данные с информацией о порции пагинируемых данных.
- `page` отвечает за размер пачки пагинации,
- `size` отвечает за размер порции.
# Ошибки
Описание
Сервер возвращает ответ со статус кодом 400 и ошибкой в формате:
{
"code": 100,
"errorMessage": "Пользователь не найден",
"data": "85"
}
Ошибки состоят из
- специфического кода `code` для идентификации ошибки
- текста `errorMessage`, который будет отображаться на клиенте
(в силу этого вся обязанность за формирование текста ошибок ложиться на серверную часть,
что позволит обеспечить гибкость механики в целом)
- и опционального поля `data`, в котором может располагаться строка с какими-то данными,
которые клиенту следует отобразить
(Например, это может быть количество секунд, через которые можно повторно отправить смс)
Специфические коды ошибок
* `100` - BadJson, неверный формат JSON от клиента
* `103` - OTPCodeRequestTooOften, слишком частые запросы на генерацию OTP-кода, в **data** - количество секунд до разблокировки
* `104` - OTPCodeInvalid, неверный OTP-код
* `105` - SendingCodeFailed, не удалось послать OTP-код
* `106` - FileUploadingError - Ошибка при загрузке файла
* `107` - NotEnoughPoints - Недостаточно баллов для списания
* `108` - NotEnoughProducts - Товар закончился
* `120` - NameNotUnique - Товар с таким именем уже существует
* `119` - RefreshTokenFailed - Не удалось обновить токен
* `121` - OTPCodeExpired - срок жизни кода истек
* `122` - BonusesWriteoffFailed - Нельзя списать больше бонусов, чем есть у пользователя
servers:
- url: https://someaddress.com
description: TODO заглушка, требуется поправить в будущем на реальный адрес.
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
paths:
/file:
post:
summary: Загрузка файла
description: Запрос на загрузку файла на сервер, используется, когда пользователь прикрепляет файлы
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: "models.yaml#/components/schemas/BinaryFile"
responses:
'200':
description: Успешный ответ с данными.
content:
application/json:
schema:
$ref: "models.yaml#/components/schemas/FileURL"
'400':
description: |
Возможные ошибки
* `106` - FileUploadingError - Ошибка при загрузке файла
content:
application/json:
schema:
$ref: "../common/models.yaml#/components/schemas/ErrorResponse"
/file/{fileUrl}:
delete:
summary: Удаление файла
parameters:
- name: fileUrl
in: path
description: Ссылка на файл
required: true
schema:
type: string
responses:
'204':
description: Файл удален
```
# Быстрые и полезные комбинации
1. Ctrl (или CMD) - Можем посмотреть что за ref у нас в методе / моделе. Наводим на ref и зажимаем комбинацию, получаем информацию о нашей ссылке. Если кликнуть по ней, то быстро перейдем по ссылке к нашей модели, на которую ссылаемся.
2. Ctrl (или CMD) + Shift + O - Позволяет найти и перейти к конкретной вкладке, либо конкретной модели / свойству.
3. Ctrl (или CMD) + Shift + P - Открывает панель команд.
4. Ctrl (или CMD) + P - Поиск файлов по имени
# Проверка спецификации API на фичу
Минимальная проверка спецификации API на фичу может быть проведена путем визуализации спецификации API при помощи комбинации `Alt+Shift+P`. Комбинацию вызывать находясь в файле `api.yaml`.
Проверить что спецификация визуализируется, все параметры отверстаны, прописаны обязательные и `nullable` поля. Запросы и ответы также отрендерены и не отображаются ошибки.
Если не рендерится сваггер, на что обратить внимание:
Табуляция. Проверьте, что все находится на своем уровне
Ссылки. Проверьте, что все ссылки корректные и они ссылаются на существующие модели.
| Проблема с табуляцией | Все ок |
| ------------- |:------------------|
|  |  |
# Проверка всей спецификации API через линтер SurfGen
Для проверки должен быть подключен `SurfGen` и подключен линтинг через `GitHub Actions`.
Для корректной работы ветки называть `feature_branchname`, к примеру, чтобы у всех веток (кроме мастера) был единый префикс.
Далее при открытии PR-а, при пуше изменений и тд будет запускать проверка спеки на соответствие всего вышеописанного.
- Если проставилась зеленая галка, то все с вашей спекой ок.
- Если видим красный индикатор, то есть проблемы и их нужно исправить.
Идем на вкладку Checks и читаем логи (build). Начинаем читать все, начиная с третьей строки. Что мы видим?
- Линтер пошел парсить схему `/common/models.yaml`
- Зашел в объект `ErrorResponse`
- В параметре `data` нашел ошибку- Выдает нам описание ошибки.
Чтобы исправить ошибку мы идем в папку common -> файл models.yaml -> находим модель `ErrorResponse`, а в ней параметр `data`. Исправляем описанную линтером ошибку.
.png)
После того, как мы запушим изменения с исправлением линтер снова запустится и продолжит проверку. Исправляем до тех пор, пока не получим зеленую галку)
# Дополнительная информация для ознакомления и советы
## Хэдеры
Основные дефолтные хэдеры:
- `Accept-Charset` - способ клиента сказать в какой кодировке нужны данные (`UTF-8`, `ASCII`, `whatever`). Обычно всегда используется `UTF-8` и менять не нужно.
- `Accept-Encoding` (аналог с сервера - `Content-Encoding`) - то, как данные от сервера закодированы, обычно речь про алгоритм сжатия. Например, `gzip`.
- `Accept-Language` (аналог с сервера - `Content-Language`) - то, какой язык хочет получить клиент. Использовать можно для мультиязычных сервисах.
- `Accept` (аналог с сервера - `Content-Type`) - Формат данных которые клиент поддерживает, эти форматы называются MIME-типами. Например, `application/json`. Такое часто бывает при передаче файлов или когда хотим открыть файл в вебе, здесь нужно правильно установить MIME-тип.
- `Cookies` - это способ хранить состояние. Как это работает:
- Сначала сервер просит клиента установить `cookies` (`Set-Cookie`).
- Клиент их отправляет серверу при обращении в хэдерах с ключом `Cookie`.
>`Cookies` могут использоваться для передачи токена. Не самый лучший способ, но такое может быть.
В таком случае обязательные параметры для таких `cookies`:
## Советы
Совет
Описание
Используйте kebab-case для URL
Вот пример для списка заказов.
Плохо: /systemOrders
или /system_orders
Хорошо: /system-orders
Используйте camelCase для параметров
Вот пример получения списка продуктов в магазине.
Плохо: /system-orders/{order_id}
или /system-orders/{OrderId}
Хорошо: /system-orders/{orderId}
Используйте множественное число для коллекций
Если вы хотите получить всех пользователей.
Плохо: GET /user
или GET /User
Хорошо: GET /users
Не используйте глаголы в URL ресурсов
Вместо этого пользуйтесь HTTP методами для описания операций.
Плохо: POST /updateuser/{userId}
или GET /getusers
Хорошо: PUT /user/{userId}
Пользуйтесь глаголами в URL операций
Например, если вы хотите переслать уведомление пользователю.
Хорошо: POST /alerts/245743/resend
Помните, что resend не является [CRUD](https://ru.wikipedia.org/wiki/CRUD) операцией. Наоборот, это функция, которая выполняет определённое действие на сервере.
Используйте camelCase для JSON свойств
Вместо этого пользуйтесь HTTP методами для описания операций.
Плохо:
```json
{
user_name: "Ванька Петров",
user_id: "1"
}
```
Хорошо:
```json
{
userName: "Ванька Петров",
userId: "1"
}
```
Используйте простой порядковый номер для версий
И всегда указывайте его на самом верхнем уровне.
Хорошо: http://api.domain.com/v1/shops/3/products
Указывайте количество ресурсов в ответе на запрос
Это свойство можно назвать total.
Плохо:
```json
{
users: [
...
], offset: 0
}
```
Хорошо:
```json
{
users: [
...
] offset: 0,
total: 34
}
```
Используйте параметры limit
и offset
И всегда указывайте его на самом верхнем уровне.
Хорошо: GET /shops?offset=5&limit=5
Потому что на фронтенде часто требуется пагинация
Не передавайте аутентификационные токены в URL
И всегда указывайте его на самом верхнем уровне.
Это очень плохая практика, потому что часто URL логгируются, и токен также сохранится.
Плохо: GET /shops/123?token=some_kind_of_authenticaiton_token
Хорошо: Вместо этого пользуйтесь заголовками. Authorization: Bearer xxxxxx, Extra yyyyy
Помните — время жизни токена нужно ограничивать
Используйте HTTP методы для CRUD операций
В этом и есть их смысл.
GET
: получение данных о ресурсах.
POST
: создание новых ресурсов и подресурсов.
PUT
: обновление существующих ресурсов.
PATCH
: обновляет только определённые поля существующих ресурсов.
DELETE
: удаляет ресурсы.
URL должен отражать структуру вложенных ресурсов
Примеры:
GET /shops/2/products
: получить список продуктов из магазина 2.
GET /shops/2/products/31
: получить детали продукта 31 из магазина 2.
DELETE /shops/2/products/31
: удалить продукт 31 из магазина 2.
PUT /shops/2/products/31
: обновить данные о продукте 31. Используйте PUT на URL ресурса, а не коллекции.
POST /shops
: создать новый магазин и вернуть данные о нём. Используйте POST на URL коллекции.