Меня зовут Данила Рачилин. Я один из разработчиков компании WiseAdvice.tech. Мы специализируемся на обслуживании и консалтинге бухгалтерских и учетных функций. В нашем контуре довольно часто встречаются различные интеграции, в большинстве случаев – это B2B-интеграции по принципу back-to-back с корпоративными системами.
Идея этой статьи появилась очень интересно. Она возникла во время одного из споров, в котором я участвовал, когда речь зашла о том, как правильно проектировать API. Ситуация старая как мир: сколько существует REST, столько и длится этот самый холивар.
Мне захотелось разобраться:
-
почему мы так стремимся «правильно» проектировать REST,
-
действительно ли мы это делаем правильно,
-
какие проблемы мы встречаем при проектировании API в стиле REST,
-
почему HTTP и REST такие, какие они есть,
-
и какие могут быть альтернативы.
Что вообще представляет собой REST? Кратко: это концепция построения сервисов, работающих поверх протокола HTTP в соответствии с определенной архитектурой.
Основные признаки REST
Можно выделить три ключевых признака, по которым мы чаще всего узнаем REST. Используются URL как идентификаторы ресурсов, HTTP-методы (глаголы) применяются по назначению, а статус-коды – корректно.
Думаю, все, кто читает эту статью, прекрасно это понимают.
Теперь давайте рассмотрим несколько примеров API и попробуем понять, какой из них действительно REST.
Первый пример. Что бросается в глаза?
Похоже, это не REST. Используется только один метод – POST. Глаголы действий указаны в названиях методов, а сами URL не содержат идентификаторов ресурсов.
Второй вариант. Может, это он?
Используются два метода: GET и POST – уже интереснее. Есть какая-то идентификация – например, во второй строке. Но тоже не совсем то.
Третий вариант. Он наиболее интересный.
Здесь представлено разнообразие HTTP-методов: GET, PUT, DELETE, POST. Тот самый пресловутый CRUD (или CRUDEL – Create, Read, Update, Delete, List). Это стандартные действия над ресурсами. Значит, здесь уже используются глаголы по назначению. Есть полноценная идентификация ресурсов. Со статус-кодами неясно, но пока опустим этот момент. Нас сейчас интересуют именно представления методов.
А что, если я скажу, что все три варианта, которые я показал, – это все-таки REST? Просто REST разного уровня зрелости.
Существует такая полезная штука – уровень зрелости REST (или Модель Ричардсона). Ее однажды придумали, чтобы классифицировать настоящие REST-сервисы по четырем уровням, начиная с нулевого.
Таким образом, приведенные мной примеры относятся к разным уровням зрелости REST. Но когда мы говорим REST, как правило, имеем в виду второй уровень.
Проблемы при проектировании REST API
Вокруг REST возникают постоянные холивары – они основаны на сложностях и недопонимании, с которыми мы сталкиваемся при проектировании API. Cамые частые проблемы: HTTP-методы и статус-коды плохо ложатся на сложную бизнес-логику.
Например, простой метод «получить список товаров», возвращающий массив данных, – это понятно и удобно. Но что, если речь идет о сложном процессе, например, оформлении заявки по паттерну «мастер» – когда нужно последовательно подгружать и обрабатывать данные? Тут уже возникают трудности. Кажется, что CRUD-подход перестает справляться.
Есть еще и проблема N+1: мы нагружаем систему избыточными операциями, потому что пытаемся получить множество дополнительных данных и ресурсов, хотя не всегда они нужны в таком объеме.
Эта проблема особенно проявляется при связях «один ко многим» – когда для каждого элемента приходится делать дополнительные запросы. Из этого вытекает третья проблема: избыточная нагрузка на систему, которая не всегда оправдана.
Чтобы понять, откуда берутся эти сложности, и почему REST устроен именно так, – сделаем небольшой экскурс в историю. Посмотрим, как все это зарождалось.
История создания HTTP и REST
В 1989 году протокол HTTP был разработан в CERN. Его первоначальная версия использовалась как протокол для электронной библиотеки. Изначально был только один метод – GET, который возвращал HTML-страницу. Пользователь просто «перелистывал» страницы, переходя по ссылкам.
Затем начался стремительный рост популярности World Wide Web – нашей любимой «паутины». В 1996 году вышла первая полноценная версия HTTP. А в 2000 году один из ключевых людей в мире веб-интерфейсов и интеграций – Рой Филдинг – представил диссертацию, в которой описал архитектуру REST.
В ней он говорит, что REST – это не что иное, как обоснование тех идей, которые были заложены в протокол HTTP с самого начала. Филдинг – соавтор протокола HTTP, и в своей диссертации он описывает именно те принципы, которые закладывались при его разработке, – но уже в виде четкой архитектурной концепции.
Этих принципов шесть. Они на рисунке.
Принципы REST: единообразие интерфейса и HATEOAS
Нас сейчас особенно интересует принцип единообразия интерфейса. Он включает четыре подпункта:
-
Идентификация ресурсов – мы уже сталкивались с этим: URL выступают в роли идентификаторов объектов. Это основа REST.
-
Работа с ресурсами через представление – речь о том, что в заголовке Content-Type мы указываем тип данных, которые передаем клиенту. Это позволяет понять формат тела ответа.
-
Самодокументированное сообщение (Self-descriptive messages) – каждое сообщение должно содержать всю необходимую информацию для его обработки. Клиенту не нужно знать контекст заранее – все должно быть понятно из самого запроса и ответа.
-
HATEOAS – самый интересный и редко используемый принцип. Это аббревиатура от Hypermedia as the Engine of Application State. Суть его в том, что сервер возвращает вместе с ресурсом информацию о связанных ресурсах и доступных действиях над ними.
Базовый ответ, соответствующий принципу HATEOAS, содержит поле links – массив ссылок. В нем перечислены идентификаторы ресурсов и указываются методы, которые можно к ним применить (например, GET, POST и т.д.).
HATEOAS и уровень зрелости REST
В итоге получается, что HATEOAS – это как раз то самое, что делает сервис по-настоящему RESTful. Если вернуться к диаграмме зрелости REST, то наличие HATEOAS означает достижение максимального, третьего уровня зрелости. Он подразумевает полную самодокументированность API: клиент может самостоятельно навигироваться по системе, не зная заранее все возможные endpoint’ы.
На практике, если вы используете HATEOAS, в Swagger или OpenAPI у вас появляется гораздо меньше необходимости – сам API уже «рассказывает» о себе. Мы возвращаем один endpoint, а дальше клиент ориентируется по ресурсу и связанным с ним ссылкам.
HATEOAS до сих пор существует и используется. Это действительно удобная и применимая концепция в определенных сценариях.
Как я сказал, когда мы говорим REST, мы чаще всего имеем в виду второй уровень зрелости. Третий уровень – с HATEOAS – существует, но применяется редко.
Кто-то из вас, разрабатывая интеграции, сервисы или предоставляя свой API, вообще задумывался о том, чтобы в 1С соблюдать HATEOAS? Хотя концепция применима, на практике мы чаще всего пишем back-to-back сервисы, где нет публичного клиента, которому нужна навигация по ресурсам, переходы между состояниями и прочие REST-сценарии. В таких системах задача простая: «сделай действие здесь и сейчас и верни правильный ответ».
Зачем вообще был нужен HATEOAS
Когда все это проектировалось, когда Рой Филдинг писал свою диссертацию, уровень систем был иным. Они ограничивались простыми CRUD-операциями, которые отлично вписывались в модель REST. Речь шла о навигации по простым медиаресурсам: страницам, изображениям, форумам, новостным статьям – всему, что можно описать через GET, PUT, DELETE, UPDATE.
Современные системы уже не такие, поэтому возникает вопрос: для чего это было нужно?
Кстати, примерно такое же выражение лица было у меня, когда я сам впервые разбирался во всем этом.
Вопросы, которые нужно задать при проектировании API
Когда мы проектируем свои сервисы, важно заранее задать себе несколько ключевых вопросов, чтобы потом не спорить в команде и не устраивать холивары на тему «а это вообще REST или нет».
-
Мы разрабатываем веб-интерфейс с фронтендом, где нужна навигация по ресурсам, сложные действия над объектами – или все ограничивается простыми CRUD-операциями?
-
Хотим ли мы упираться в рамки REST, если это усложнит логику и сделает API менее понятным?
-
Нужно ли нам реализовывать HATEOAS?
Учитывая, что большинство наших интеграций – это back-to-back взаимодействие, вряд ли HATEOAS нам действительно нужен. Тогда возникает четвертый вопрос: а можно ли вообще отказаться от REST? Если мы не получаем от него преимуществ, зачем следовать его принципам?
Альтернативой может стать RPC-стиль проектирования API.
RPC-стиль: принципы и преимущества
На рисунке основные объективные плюсы RPC. Мы используем один URL – своего рода «универсальный исполнитель» (endpoint-executor), на который отправляются все запросы. Для всех вызовов используется один HTTP-метод – POST. Мы не тратим время на выбор глагола, не переживаем за статус-коды.
Параметры и название метода передаются в теле запроса как служебные поля.
Таким образом, на один endpoint приходится множество методов. На все запросы возвращаем HTTP 200 OK, а логику статуса переносим внутрь тела ответа.
Среди преимуществ – сама концепция RPC (Remote Procedure Call), которая означает «вызов удаленной процедуры». Мы не ограничиваем себя глаголами, а просто вызываем нужную функцию – как привыкли в 1С: вызвали общий модуль, передали параметры, получили результат и пошли дальше.
Пример RPC-запроса и его особенности
Когда мы проектируем back-to-back сервисы для корпоративных интеграций, все чаще оказывается, что HATEOAS нам не нужен.
Нам не требуется навигация по ресурсам – нужно просто вызвать удаленную процедуру, передать данные, получить валидный ответ и идти пить чай.
Рассмотрим пример типичного RPC-запроса.
Как я уже говорил, используется один HTTP-метод – POST – и один общий endpoint, например, /command.
В теле запроса передаются:
-
служебное поле version – для версионирования API;
-
method – название вызываемого метода;
-
params – структура параметров, например, объект product.
Ответ выглядит аналогично: поле result содержит данные результата, а статус обработки можно передать внутри тела.
Мы можем всегда возвращать HTTP 200 OK, а свою семантику ошибок и статусов реализовать на уровне приложения. Нам не обязательно использовать 201, 404 или другие HTTP-коды – мы не привязываемся к их строгой семантике.
Более того, RPC-стиль не зависит от транспорта. В отличие от REST, который неразрывно связан с HTTP (потому что Рой Филдинг, будучи соавтором HTTP, строил REST вокруг его возможностей), RPC может работать поверх разных протоколов: HTTP, WebSockets, gRPC, message brokers и других.
Это дает гибкость на будущее.
Недостатки RPC-стиля
Однако у RPC есть и обратная сторона: он лишен некоторых преимуществ, которые дает REST. Речь о таких вещах, как кэширование, маршрутизация, балансировка нагрузки и мониторинг. Когда все запросы идут на один endpoint – гибкость снижается. Да, масштабирование и развитие идут быстро: добавил метод – и готово. Не нужно каждый раз проектировать сложные URI или думать, какой HTTP-метод использовать. Но именно в этом и кроется подвох.
Один универсальный обработчик может превратиться в «черный ящик», который становится сложно обслуживать по мере роста числа методов. Появляется проблема с кэшированием: POST-запросы по умолчанию не кэшируются, потому что они неидемпотентны. Сервер всегда возвращает свежий ответ, что создает избыточную нагрузку. Маршрутизация и балансировка тоже усложняются. Все запросы приходят на один адрес – и если их много, можно исчерпать лимит входящих соединений на веб-сервере.
Если бы у нас было несколько отдельных сервисов, мы могли бы распределить их по разным пулам, снизив нагрузку.
Мониторинг – еще одна сложность. Но ее можно решить: если в строке запроса есть параметр URI method, мы можем логировать его на уровне веб-сервера. Тогда, даже не читая тело, сможем понять, какие методы вызываются чаще, где растет нагрузка.
То есть все проблемы решаемы, но требуют дополнительных усилий – в отличие от REST, где многие из них решаются «из коробки».
Гибриды и практика: REST + RPC
Исходя из этого, появляются более гибкие и практичные подходы. Мы – разработчики, и наша фантазия бьет ключом. Мы не обязаны слепо следовать каким-то стандартам, если они ухудшают нашу систему. Поэтому на практике часто возникают гибриды REST и RPC. Например, используются GET и POST для базовых операций, а сложные действия – через RPC-подобные вызовы на отдельных endpoint’ах. Такой подход имеет право на жизнь.
Кстати, мы всегда смотрим на вендора. У него много удачных решений, которые мы с успехом перенимаем. На мой взгляд, одно из самых технологичных – облачная подсистема Fresh, где разработчики поняли: «REST – это не догма». Они осознали, что их системы будут взаимодействовать с разными внешними системами и выбрали подход, который лучше всего подходит под задачу.
Пример от вендора: Fresh и Extended API
Уточню немного контекст. FRESH – это облачная платформа, которая работает на основе механизма разделения данных в одной информационной базе. Для тех, кто не в курсе: в одной информационной базе может быть несколько областей – по сути, несколько независимых информационных баз, объединенных в единую инфраструктуру. FRESH – это комплекс программных продуктов, основным из которых является менеджер сервиса, управляющая конфигурация всей этой системы.
Для внешнего управления инфраструктурой вендор реализовал API, в котором применил RPC-стиль взаимодействия – он называется Extended API. По ссылке https://its.1c.ru/db/freshsm можно перейти в документацию ИТС, посмотреть, как устроен подход и как все это выглядит на практике.
На рисунке – скриншот из конфигурации: там есть обработка методов ExtAPI, где описаны все доступные вызовы. Это позволяет наглядно показать, как устроено API и какие элементы используются.
Мы видим, что в выделенной зеленой части находятся данные бизнес-логики – параметры методов. Красным цветом выделены служебные поля: например, version – для версионирования API. Есть также режим отладки – debugMode. Эти поля не строго фиксированы, внутри команды нужно договориться, какие поля использовать и как их интерпретировать.
В самой нижней части – ответ, который возвращается. Он тоже состоит из регламентированных полей, согласованных при проектировании. Это responseCode – код возврата, который, если посмотреть справа, отличается от HTTP. Но при этом взяты за основу стандартные HTTP-коды: например, 10200.
Первые две цифры добавлены к классическим кодам – видимо, для расширения семантики ошибок.
Есть поле error – флаг ошибки, и message – текстовое описание. Все понятно и прозрачно. А выше – сам обработчик метода. По сути, это и есть вся «магия»: название метода, например, srvFile.isCompleteMultipart, напрямую связано с вызовом удаленной процедуры в ядре. Вся архитектура Extended API построена примерно так. Это лишь малая часть, но она показательна.
При этом вендор не следует REST слепо. Где уместно – использует CRUD: в названиях методов встречаются create, delete, list. Где это неудобно, нелогично или неуместно – спокойно отходит от паттернов: например, newPart, newMultipart. И при этом все работает стабильно.
Почему это так сделано? В менеджере сервиса очень много разных интерфейсов, которые сильно отличаются друг от друга. Если на каждый «навешивать» REST, который там малоприменим, это только усложнит и замедлит интеграцию. Гибкость важнее догм.
Заключение: REST – не догма
В завершение хотелось бы сказать, что REST – это не свод непреложных законов, которые нужно слепо и бездумно соблюдать. При проектировании API важно вместе с командой понимать зачем мы его делаем, какие у нас требования и какие могут быть подводные камни. Например:
-
Хотим ли мы в будущем сменить транспорт – перейти с HTTP на WebSockets, message brokers и т.п.? Тогда, возможно, стоит изначально закладываться в RPC-подобную архитектуру.
-
Или у нас внешняя система с навигацией, где важна идентификация ресурсов через URL? Тогда REST подойдет идеально. Например, B2B-портал для оформления заявок от поставщиков – вот где REST действительно «садится» отлично. Там нужна навигация, самодокументированность, четкая структура.
Проектируйте удобное и понятное API, чтобы оно вас никогда не «пятисотило», а работало стабильно и предсказуемо.
*************
Статья написана по итогам доклада (видео), прочитанного на конференции INFOSTART TECH EVENT.
Вступайте в нашу телеграмм-группу Инфостарт