О REST

2019-12-28

Я очень не люблю REST. Тот самый, который Representational State Transfer. Я предпочитаю JSON-RPC.

JSON-RPC — это какой-никакой, но хоть какой-то стандарт. Основанный на давным-давно существующей концепции RPC. И JSON-RPC никак не привязан к транспортному протоколу. Он может работать и через HTTP, и через WebSocket, и через любую систему отправки сообщений.

REST — это не стандарт. Это «архитектурный стиль». И он прочно привязан к HTTP.

REST вообще не мыслим без HTTP. Самое понятие ресурсов — это из URL. Соответственно, путь до ресурса — это path в URL. Действия над ресурсами задаются через методы-глаголы HTTP.

URL

REST не так прост, как кажется. Есть несколько уровней крутости REST. Самый распоследний — это HATEOAS. Hypermedia as the engine of application state. Это когда помимо самих данных нужно ещё проставлять ссылки на связанные данные. Хотя бы на соседние страницы в многостраничной выдаче. Это не так просто сделать, поэтому обычно на эту крутость забивают.

glory

REST — это про сущности, коллекции сущностей и CRUD. Но CRUD сущностей — это очень мало. В жизни нужно больше. И приходится «натягивать» это на REST.

verbs

Например, пусть у нас есть REST интерфейс для управления виртуальными машинами. У сущности «экземпляр виртуальной машины» будет свойство state. Со значениями, как минимум "running" и "stopped".

Но виртуальную машину можно остановить разными способами. Завершить «мягко». Или завершить «жёстко». И это не есть просто перевод state из "running" в "stopped". Одним PATCH не обойтись.

Как это обойти? Выдумать ещё дополнительные значения для state?

В общем случае у сущностей, как у нормальных объектов, могут быть свои произвольные методы. И их хотелось бы вызывать.

Для вызова методов (и не только) к REST придумали «расширение» Restful Objects. Кроме объектов и их свойств тут ещё есть actions. То есть можно сделать что-нибудь вроде POST /vms/<id>/actions/shutdown/invoke.

Ну и вообще, свойства ресурса — это отдельные ресурсы или нет? С вложенным path? А если это отдельные ресурсы, нужно ли их значения выводить, когда делают GET родительского ресурса? Как определить, когда нам нужен родительский ресурс без дочерних деталей, а когда нам нужен родительский ресурс со всеми вложенными ресурсами?

В документо-ориентированных БД тоже встаёт такая дилемма. Включать связанный документ в виде вложенного свойства или хранить в отдельной коллекции? Но в БД мы лишь однажды принимаем это решение, определяем схему БД, и живём с этим дальше. А в REST мы вроде как можем и так и так. Нужна ли такая гибкость?

А тот же PATCH? Как его правильно реализовывать? Как удалять свойства? Передавать null? REST ничего не говорит, потому что это не стандарт, а лишь «стиль».

А как вообще делать банальные вещи вроде пагинации, сортировки, фильтрации? Ну вроде как ясно, что нужно добавлять параметры запроса. Те, которые после вопросика. Но какие именно параметры? Как кодировать эти сортировки и фильтры? REST ничего не говорит.

А как залить картинку и задать для неё свойства? Кодировать изображение в base64 строку в JSON? Или тело картинки сделать отдельным «подресурсом», и создавать картинку в два разных запроса? Или один multipart запрос c JSON свойствами и самим изображением?

А как вам весьма типичный интерфейс с множественным выбором и bulk edit? Как это вообще реализовать в REST? Как-то нет там методов, чтобы сделать DELETE или PUT сразу на много ресурсов. Приходится придумывать дополнительные псевдоресурсы, принимающие массивы изменений. Это всё ещё REST?

bulk edit

Частично все эти неоднозначности стандартизирует, например, Open Data Protocol (OData) от Microsoft. Это уже не «стиль», а действительно протокол. Тут есть правила доступа к объектам, коллекциям, свойствам, фильтрации, вызова методов и batch операций (через multipart POST). Но URL path выглядят весьма непривычно (например: serviceRoot/People('russellwhyte')/Trips(1003)/PlanItems(21)/Microsoft.OData.SampleService.Models.TripPin.Flight). И вообще, типов ресурсов становится что-то сильно много.

REST — не прост. И часто он заставляет задумываться о таких вещах, о которых в RPC думать особо и не надо. В RPC просто делаешь ещё один метод, если надо, и всё. А в REST ещё и выдумываешь, а какой псевдоресурс ещё создать, а какой JSON туда засылать, какие HTTP методы реализовывать и какие параметры запроса принимать. Слишком много выдумывать.

Конечно, в RPC тоже нужно заботиться о минималистичности, лаконичности и согласованности API. Но это почти аналогично разработке самых обычных API. Определяем нужный набор непротиворечивых и не пересекающихся по функциональности методов. Функциональный подход, так сказать.

А в REST нужно придумывать ресурсы. Как они соотносятся друг с другом. Есть ли у них методы кроме CRUD. Объектный подход, так сказать.

Лично мне REST больше мешает, чем помогает. И меня дико раздражает, что сейчас любое API, работающее через HTTP, называют REST, даже если это не так. Или пытаются натянуть на REST. И все инструменты для HTTP API, от Swagger до AWS API Gateway, заточены под REST и только под него. А если я хочу JSON-RPC?