Хабрахабр

REST? Возьмите тупой JSON-RPC


В последнее время на Хабре разгорелось много споров по поводу того, как правильно готовить REST API.

Вместо того, чтобы бушевать в комментариях, подумайте: а нужен ли вам REST вообще?
Что это — осознанный выбор или привычка?

Возможно, именно вашему проекту RPC-like API подойдет лучше?

0?
Это простой stateless-протокол для создания API в стиле RPC (Remote Procedure Call).
Выглядит это обычно следующим образом. Итак, что такое JSON-RPC 2.

У вас на сервере есть один единственный endpoint, который принимает запросы с телом вида:

, "id": 1}

И отдает ответы вида:

{"jsonrpc": "2.0", "result": {"likes": 123}, "id": 1}

Если возникает ошибка — ответ об ошибке:

{"jsonrpc": "2.0", "error": {"code": 666, "message": "Post not found"}, "id": "1"}

И это всё!

Бонусом поддерживаются batch-операции:

Request: [ {"jsonrpc":"2.0","method":"server.shutdown","params":{"server":"42"},"id":1}, {"jsonrpc":"2.0","method":"server.remove","params":{"server":"24"},"id":2}
] Response: [ {"jsonrpc":"2.0","result":{"status":"down"},"id":1} {"jsonrpc":"2.0","error":{"code":1234,"message":"Server not found"},"id": 2}
]

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

Также клиент может отправлять «нотификации» — запросы без поля «id», которые не требуют ответа от сервера:

{"jsonrpc":"2.0","method":"analytics:trackView","params":{"type": "post", "id":"123"}},

Протокол настолько простой, что написать свою реализацию займет пару часов. Библиотеки для клиента и сервера есть, наверное, под все популярные языки.
Если нет — не беда.

Работа с RPC-клиентом, который мне первым попался на npmjs.com, выглядит так:

client.request('add', [1, 1], function(err, response) { if (err) throw err; console.log(response.result); // 2
});

Профиты

Согласованность с бизнес-логикой проекта

Во-первых, можно не прятать сложные операции за скудным набором HTTP-глаголов и избыточными URI.
Есть предметные области, где операций в API должно быть больше чем сущностей.
Навскидку — проекты с непростыми бизнес-процессами, gamedev, мессенджеры и подобные realtime-штуки.

Да даже взять контентный проект вроде Хабра…
Нажатие кнопки "↑" под постом — это не изменение ресурса, а вызов целой цепочки событий, вплоть до выдачи автору поста значков или инвайтов.
Так стоит ли маскировать post.like(id) за PUT /posts/{id}/likes?

Здесь также стоит упомянуть CQRS, с которым RPC-шный API будет смотреться лучше.

Во-вторых, кодов ответа в HTTP всегда меньше, чем типов ошибок бизнес-логики, которые вы бы хотели возвращать на клиент.
Кто-то всегда возвращает 200-ку, кто-то ломает голову, пытаясь сопоставить ошибки с HTTP-кодами.
В JSON-RPC весь диапазон integer — ваш.

JSON-RPC — стандарт, а не набор рекомендаций

Очень простой стандарт.

Данные запроса могут быть:

REST

RPC

В URI запроса

---

В GET-параметрах

---

В HTTP-заголовках

---

В теле запроса

В теле запроса

Данные ответа могут быть:

REST

RPC

В HTTP-коде ответа

---

В HTTP-заголовках

---

В теле ответа (формат не стандартизирован)

В теле ответа (формат стандартизирован)

Остается POST /api. POST /server/{id}/status или PATCH /server/{id}?
Это больше не имеет значения.

Нет никаких best practices с форумов, есть стандарт.
Нет разногласий в команде, есть стандарт.

Однако… Конечно же, качественно реализованный REST API можно полностью задокументировать.

Знаете, что и где нужно передать в запросе к Github API, чтобы получить объект reactions вместе с issue?

Accept: application/vnd.github.squirrel-girl-preview

Хорошо это или плохо? Решайте сами, гуглите сами. Стандарта нет.

Независимость от HTTP

В теории, принципы REST можно применять не только для API поверх HTTP.
На практике все по-другому.

Да хоть TCP.
Тело JSON-RPC запроса можно прямо в сыром виде бросить в очередь, чтобы обработать позже. JSON-RPC over HTTP безболезненно переносится на JSON-RPC over Websocket.

Больше нет проблем от размазывания бизнес-логики по транспортному уровню (HTTP).

HTTP 404

REST

RPC

Ресурса с таким идентификатором нет

---

Здесь API нет

Здесь API нет

Производительность

JSON-RPC пригодится, если у вас есть:
— Batch-запросы
— Нотификации, которые можно обрабатывать асинхронно
— Вебсокеты

Но с ним — чуть легче. Не то, чтобы это все нельзя было сделать без JSON-RPC.

Подводные камни

HTTP-кеширование

Если вы собираетесь кешировать ответы вашего API на уровне HTTP — RPC может не подойти.
Обычно это бывает, если у вас публичное, преимущественно read-only API.
Что-то вроде получения прогноза погоды или курса валют.

Если ваше API более «динамичное» и предназначено для «внутреннего» использования — все ок.

access.log

Все запросы к JSON-RPC API в логах веб-сервера выглядят одинаково.
Решается логированием на уровне приложения.

Документирование

Для JSON-RPC нет инструмента уровня swagger.io.
Подойдет apidocjs.com, но он гораздо скромнее.
Впрочем, документировать такой простой API можно хоть в markdown-файле.

Stateless

И будете правы. «REST»  — об архитектуре, а не глаголах  HTTP — возразите вы.

«Stateless». В оригинальной диссертации Роя Филдинга не указано, какие именно глаголы, заголовки и коды HTTP нужно использовать.
Зато в ней есть волшебное слово, которое пригодится даже при проектировании RPC API.

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

Для контраста вспомним по-настоящему statefull протокол  —  FTP. Делая RPC API поверх веб-сокетов, может возникнуть соблазн заставить сервер приложения хранить чуть больше данных о сессии клиента, чем нужно.
Насколько stateless должен быть API, чтобы не причинять проблем?

3. Клиент: [открывает TCP-соединение]
Сервер: 220 ProFTPD 1. 1 Server (ProFTPD)
Клиент: USER anonymous
Сервер: 331 Anonymous login ok, send complete email address as your password
Клиент: PASS user@example.com
Сервер: 230 Anonymous access granted, restrictions apply
Клиент: CWD posts/latest
Сервер: 250 CWD command successful
Клиент: RETR rest_api.txt
Сервер: 150 Opening ASCII mode data connection for rest_api.txt (4321 bytes)
Сервер: 226 Transfer complete
Клиент: QUIT
Сервер: 221 Goodbye.

FTP-сервер помнит, что клиент уже прошел аутентификацию в начале сеанса, и помнит, в каком каталоге сейчас «находится» этот клиент. Состояние сеанса хранится на сервере.

Такой API сложно разрабатывать, дебажить и масштабировать. Не делайте так.

В итоге

Возьмите JSON-RPC 2.0, если решитесь сделать RPC API поверх HTTP или веб-сокетов.
Можете, конечно, придумать свой велосипед, но зачем?

Возьмите GraphQL, если он правда вам нужен.

Возьмите gRPC или что-то подобное для коммуникации между (микро)сервисами, если ваш ЯП это поддерживает.

Теперь вы, по крайней мере, выберете его осознанно. Возьмите REST, если нужен именно он.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть