Меня все сильнее раздражает, сколько людей готовы записывать в REST API любой интерфейс на основе HTTP. Сегодня приведу в качестве примера SocialSite REST API. Это же вызовы удаленных процедур (RPC). Он просто выкрикивает RPC. Связность между элементами на экране настолько сильная, что это творение заслуживает рейтинга X.
Ну что еще нужно сделать, чтобы в архитектурном стиле REST четко читалось: гипертекст – это обязательное условие? Иными словами, если движок, на котором основано состояние приложения (и, следовательно, API) работает не на гипертексте, то он не может быть RESTful и, следовательно, не может быть REST API. Точка. Может быть, где-то есть корявый мануал, который нужно отредактировать?
--Рой Филдинг, автор термина REST
REST – должно быть, за всю историю компьютерного программирования не было другого термина, которым бы настолько широко злоупотребляли.
Мне так и близко не приходит на ум ничего, что могло бы с ним в этом сравниться.
Сегодня, когда речь заходит о REST, практически наверняка обсуждают API на основе JSON, работающим по HTTP.
Если вы видите вакансию, в которой упоминается REST, либо вам попадается компания, в которой обсуждаются руководящие принципы REST, едва ли будут упомянуты гипертекст или гипермедиа; вместо этого речь там пойдет о JSON, GraphQL(!) и т.п.
Лишь немногочисленные упрямцы проворчат: но эти JSON API совсем не REST!
В этом посте я изложу вам краткую, неполную и в основном ошибочную историю REST, а также как мы дошли до жизни такой, когда значение этого термина оказалось почти полностью извращено и приравнено к RPC, именно в качестве альтернативы которым исходно создавался REST.
Откуда взялся REST?
Термин REST, аббревиатура, расшифровываемая как REpresentational State Transfer (передача репрезентативного состояния) впервые был употреблен в главе 5 диссертации Филдинга на соискание степени PhD. Филдинг описывал сетевую архитектуру тогда еще относительно новой Всемирной Паутины и сравнивал ее с другими возможными сетевыми архитектурами, особенно с выдержанными в стиле RPC (вызов удаленных процедур).
Важно понимать, что на момент написания этой диссертации (1999-2000) никаких JSON API еще не существовало: Филдинг описывал Веб в том виде, в каком он существовал тогда. Обмен HTML происходил по протоколу HTTP, когда человек «сёрфил» в Вебе. Нотация JSON еще даже не была создана, а до широкого принятия JSON оставалось еще около 10 лет.
Парадигма REST описывала сетевую архитектуру и была определена в терминах таких ограничений, которые накладываются на API и должны соблюдаться, чтобы считалось, что данный API соответствует стилю REST. Диссертация написана академичным языком, из-за чего вокруг этой темы сразу возникла некоторая путаница. Тем не менее, текст достаточно ясен, чтобы типичный разработчик мог его понять.
Суть REST: единый интерфейс и HATEOAS
В REST заключено множество ограничений и концепций, но есть одна критически важная идея, которая, на мой взгляд, является определяющей и наиболее отличительной характеристикой REST, если сравнивать эту парадигму с другими возможными сетевыми архитектурами.
Это ограничение обычно называют единый интерфейс, а внутри этого ограничения заложена еще более конкретная идея использовать гипермедиа как двигатель состояния приложения (HATEOAS). Сам Филдинг предпочитает называть этот фактор «гипермедийным ограничением».
Чтобы понять это ограничение, требующее работать с единым интерфейсом, давайте рассмотрим два HTTP-отклика, возвращающих информацию о банковском счете. Первый отклик будет на HTML (гипертекст), а второй на JSON:
HTML-отклик
HTTP/1.1 200 OK
<html>
<body>
<div>Account number: 12345</div>
<div>Balance: $100.00 USD</div>
<div>Links:
<a href="/accounts/12345/deposits">deposits</a>
<a href="/accounts/12345/withdrawals">withdrawals</a>
<a href="/accounts/12345/transfers">transfers</a>
<a href="/accounts/12345/close-requests">close-requests</a>
</div>
<body>
</html>
JSON-отклик
HTTP/1.1 200 OK
{
"account_number": 12345,
"balance": {
"currency": "usd",
"value": 100.00
},
"status": "good"
}
Ключевая разница между двумя этими откликами (и причина, по которой HTML-отклик соответствует REST, а JSON-отклик – нет, такова:
HTML-отклик полностью сам себя описывает
Нормальный гипермедийный клиент, получающий такой отклик, не знает, что такое «банковский счет», что такое «баланс» и т.д. Он лишь умеет правильно отображать гипермедиа, то есть, HTML.
Клиент ничего не знает о конечных точках API, ассоциированного с этими данными – кроме того, что о них указано в URL и гипермедийных управляющих элементах (ссылках и формах), которые описаны в самом HTML. Именно состояние ресурса меняется так, что меняется и набор действий, которые можно совершить с данным ресурсом (например, доступен ли на счете овердрафт). В таком случае изменится и HTML-отклик, который отобразит новый набор доступных действий.
Клиент отобразит этот новый HTML, совершенно не представляя, что значит «овердрафт», даже не имея представления, что такое «банковский счет».
Именно таким образом гипертекст является двигателем для состояния приложения: HTML-отклик «тащит с собой», прямо внутри себя, всю информацию об API, необходимую для дальнейшего взаимодействия с системой.
Теперь рассмотрим для сравнения, что происходит со вторым откликом, написанным на JSON.
В данном случае сообщение не описывает само себя. Нет, клиент должен знать, как интерпретировать поле status, и только тогда он сможет отобразить нужный пользовательский интерфейс. Кроме того, клиент должен знать, какие действия можно совершать со счетом, но опираться при этом на информацию, передаваемую «вне полосы», то есть, содержащуюся в URL, параметрах и т.д. Таким образом, она должна добываться из иных источников, вне отклика, например, из документации по swagger API.
Отклик JSON сам себя не описывает, и в нем не закодировано состояние гипермедийного ресурса. Соответственно, он не удовлетворяет требованию REST о едином интерфейсе и не является RESTful.
Изобретатель: REST API должны работать на основе гипермедиа
Филдинг в статье Rest APIs Must Be Hypermedia Driven пишет:
Вход в REST API должен происходить без каких-либо априорных знаний кроме исходного URI (закладки) и набора стандартизированных MIME-типов, приемлемых для целевой аудитории (т.е., ожидается, что их поймет любой клиент, который может использовать этот API). С этого момента и в дальнейшем все переходы из состояния в состояние в приложении должны диктоваться выбором клиента из вариантов, предоставляемых сервером. Все эти варианты должны содержаться в полученном представлении или подразумеваться, исходя из того, как пользователь оперирует этими представлениями
Итак, в REST-системе вы должны быть в состоянии войти в систему через единственный URL, и, начиная с этой точки, вся навигация и действия, предпринимаемые вами в системе, должны обеспечиваться на уровне гипермедиа, которые полностью сами себя описывают. Например, при помощи ссылок и HTML-форм. Миновав точку входа в грамотно сделанную REST-систему, клиент API не должен нуждаться ни в какой дополнительной информации о вашем API.
Именно этим объясняется невероятная гибкость REST-систем: поскольку все отклики сами себя описывают, и в них зашифрованы все действия, доступные в настоящий момент – не приходится беспокоиться больше ни о чем, например, о версионировании вашего API!
Фактически, вам даже не требуется его документировать!
Если что-то изменится, то изменится и гипермедийная составляющая откликов, вот и все.
Это исключительно гибкая и инновационная концепция для построения распределенных систем.
Отраслевики: Лол, да нет же, REST API - это JSON
Сегодня большинство веб-разработчиков и компаний сочтут за REST API именно второй пример.
Вероятно, первый отклик для них даже не тянет на отклик от API. Это просто HTML. (Бедный HTML. Никто его не уважает.)
API – это всегда JSON или, может быть, при некоторых изысках могут быть похожи на Protobuf, правда?
Неправда.
Все вы неправы, и от этого вам должно быть скверно.
Первый отклик – это настоящий отклик API, и, фактически, именно он должен именоваться RESTful!
Второй отклик на самом деле написан в стиле вызова удаленных процедур (RPC), бывают и такие API. Клиент и сервер связаны, точно, как тот SocialSite API, на который Филдинг жаловался еще в 2008 году: клиенту требуется дополнительная информация о том ресурсе, с которым он работает, и эта информация должна быть выведена из каких-то других источников кроме самого JSON-отклика.
По духу этот API практически противоположен REST.
Давайте называть такой стиль API псевдо-REST.
Как REST превратился в псевдо-REST
Как же мы дошли до жизни такой, что API, очевидно не являющиеся RESTful, называются RESTful среди 99,9% представителей нашей отрасли?
Это забавная история:
Рой Филдинг опубликовал свою диссертацию в 2000 году.
Примерно в то же время вышел протокол XML-RPC, явно вдохновленный RPC, а вскоре он стал все активнее использоваться в качестве средства для построения API с применением HTTP. XML-RPC входил в состав более крупного проекта от Microsoft под названием SOAP. Протокол XML-RPC состоялся в рамках долгой традиции RPC-образных протоколов, созданных преимущественно в мире энтерпрайза. Они изобиловали статической типизацией, и еще туда был подброшен ранний XML-максимализм.
Также на тот момент формировался AJAX или «Асинхронный JavaScript и XML». Обратите внимание: и здесь XML. Сегодня общеизвестно, что AJAX позволяет браузеру отправлять HTTP-запросы на сервер в фоновом режиме, а отклики обрабатывать непосредственно в JavaScript. Так был открыт целый новый мир веб-программирования.
Вопрос был в том, как должны выглядеть такие запросы. Очевидно, они должны были состоять из XML. Это ведь прямо в названии указано. А тут вышел этот новый стандарт SOAP/XML-RPC, так может быть, все как раз и сошлось?
Может быть, REST пригоден для работы с веб-сервисами?
Некоторые отмечали, что архитектура Веба устроена иначе, нежели ее описывал Филдинг. Поэтому возникли вопросы, следует ли предпочесть REST, а не SOAP, для обращения к так называемым «веб-сервисам». Веб зарекомендовал себя как очень гибкая среда, в нем росли поборники новых законов, поэтому возникла идея: а вдруг REST, та самая сетевая архитектура, которая хорошо работала с браузерами и удобна для человека, хорошо состыкуется и с API.
Звучало правдоподобно, особенно с учетом того, что формат XML тогда как раз использовался для работы с API. XML определенно страшно похож на HTML, правда? Можно представить себе XML API, удовлетворяющий всем ограничениям REST, вплоть до единого интерфейса.
Поэтому все взялись исследовать и этот вектор.
Пока все это происходило, как раз нарождалась и еще одна важная технология:
JSON. Это была нотация, (буквально) транслировавшая JavaScript в Java для работы с SOAP/RPC-XML: просто, динамично, легко. Сегодня, когда JSON является преобладающим форматом для большинства веб-API, даже сложно поверить, что JSON закрепился не сразу, ему потребовалось некоторое время на раскачку. На дворе уже стоял 2008, а дискуссии о разработке API касались в основном XML, а не JSON.
Формализация REST API
В 2008 году Мартин Фаулер опубликовал статью, в которой популяризовал модель зрелости Ричардсона. Эта модель позволяет определить, насколько соответствует REST данный конкретный API.
В этой модели предлагалось четыре «уровня», и первым из них шел «старый добрый XML», также прозванный «болотом» или «The Swamp of POX».
Начиная с этого уровня, модель API могла считаться все более «зрелой» и соответствующей REST, по мере того, как в ней брались на вооружение следующие идеи:
Уровень 1: ресурсы (напр., URL-компоновка, учитывающая состояние ресурсов – в противовес непрозрачной URL-компоновке, как в XML-RPC).
Уровень 2: HTTP-методы (правильное использование GET, POST, DELETE, т.д.).
Уровень 3: Гипермедийные элементы управления (напр., ссылки).
Именно на уровне 3 в дело вступает единый интерфейс, именно поэтому данный уровень считается наиболее зрелым, на нем REST действительно «предстает во всей красе».
REST рулит, как бы...
К сожалению для концепции REST, в то время произошли две вещи:
Все переключились на JSON.
Все остановились на уровне 2 в модели зрелости Ричардсона.
JSON стремительно воцарился в мире веб-сервисов и API, поскольку технология SOAP/XML-RPC была так безнадежно переусложнена. JSON был прост, «работал и все», был удобочитаем и легко понятен.
Когда произошли эти перемены, мир веб-разработки решительно сбросил оковы убеждений, присущих J2EE, отмежевавшись от SOAP/XML-RPC и оставив эту технологию исключительной прерогативой энтерпрайза.
Поскольку REST-подход не был так тесно связан с XML, как SOAP/XML-RPC, а также не предъявлял такого количества формальных требований к конечным точкам, JSON, естественно, лучше всего раскрылся на поле REST – и это произошло стремительно.
В процессе этих ключевых изменений все яснее осознавалась следующая вещь: в модели зрелости Ричардсона большинство JSON API останавливались на уровне 2.
Некоторым удавалось прорваться на уровень 3, поскольку в их отклики включались гипермедийные элементы управления, но почти по всем этим API по-прежнему приходилось публиковать документацию, а это означало, что уровень «REST во всей красе» не достигнут.
Пока JSON выходил на лидирующие позиции в качестве формата откликов, должен был прозвенеть еще один громкий звоночек: очевидно, что JSON – это не гипертекст. Поверх него можно навесить гипермедийные элементы управления, но это неестественно. XML хотя бы в некотором роде похож на HTML, поэтому казалось правдоподобным, что с его помощью будет возможно создавать гипермедиа.
А JSON это просто... данные. Добавить в него гипермедийные элементы управления неудобно, эта практика не стандартизирована и редко используется так, как описано в терминах ограничений единого интерфейса.
Несмотря на эти сложности, термин REST закрепился: REST был противоположен SOAP, JSON API это не SOAP, следовательно, JSON API это REST.
Вот так в одном предложении выражено, как мы дошли до жизни такой.
Войны REST
Несмотря на то, что мир JSON API так и не дошел до непротиворечивой реализации API, по-настоящему заслуживающих называться REST, было множество стычек по-поводу того, являются ли «полноценным REST» те псевдо-REST API, с которыми приходится иметь дело: когда аргументы накладываются поверх шаблонов URL, когда и какие HTTP-методы уместны для конкретных действий, бушевали баталии о MIME-типах и так далее.
В те времена я был еще молод, вся эта история поражала меня как непонятная, пуританская и сеющая раздоры, поэтому я, по большому счету, поставил крест на всей идеи REST: она казалась чем-то унизительным, причем, из-за нее люди воевали в Интернете.
О чем я редко встречал упоминания (а встречая – не понимал), так это о концепции единого интерфейса, и о том, насколько критически важен такой интерфейс в REST-системе.
Так и было до тех пор, пока я не написал библиотеку intercooler.js, и нашлись умные ребята, рассказывавшие мне, что она RESTful – так что я вновь заинтересовался этой идеей.
RESTful? Да это же обычный JSON API, как моя слепленная на коленке библиотека для фронтенда может быть RESTful?
Так что я присмотрелся к ней, перечитал на свежую голову диссертацию Филдинга и обнаружил – вот это да! Мало того, что intercooler был RESTful, так еще и все те "RESTful" JSON API, с которыми я имел дело, никак не могли считаться RESTful!
Тогда я стал парить весь Интернет моими статьями и запарил до слез:
Наконец, та статья, которую вы сейчас читаете.
REST сегодня
В конце концов, большинство из тех, кто пытался добавлять гипермедийные инструменты управления к JSON API, устали от этого – и отступились. Да, в некоторых специализированных ситуациях (например, при разбивке на страницы) такие контролы работали хорошо, но так и не достигли той широкой и очевидной применимости, какая была характерна для REST в нормальном человеческом Интернете. (У меня есть теория, почему так произошло.)
Сошлись на том, что вполне можно оставаться в таком псевдо-REST состоянии, когда смысл REST медленно закреплялся в виде представления «это JSON API, соответствующий уровням 1 или 2 модели RMM». Но всегда оставалась возможность, что удастся прорваться на уровень 3, и даже к REST во всей красе.
Затем выстрелили Одностраничные Приложения (SPA).
Когда это произошло, веб-разработка окончательно отцепилась от исходной базовой REST-архитектуры. Вся сетевая архитектура networking SPA-приложений перешла к модели в стиле JSON RPC. Кроме того, поскольку такие приложения были достаточно сложны, разработчики стали специализироваться – кто-то на клиентской части, кто-то на серверной.
Те, кто разрабатывал клиентскую часть (фронтенд) определенно не занимались никаким REST: они работали с JavaScript, собирали DOM-объект и по мере необходимости вызывали разные AJAX API. Это гораздо более походило на написание толстых клиентов, чем на что-либо из раннего Веба.
Разработчики серверной части по-прежнему в некоторой степени интересовались сетевой архитектурой и, описывая свою текущую работу, продолжали называть ее «REST».
Пусть они и занимались такими вещами, как публикация swagger-документации для своих REST API или жаловались на перемешивание этих REST API, всего этого бы не происходило, если бы они на самом деле создавали API в стиле REST.
Наконец, в конце 2010-х, всем это надоело: REST, даже в его псевдоформе, просто не успевали удовлетворять потребности постоянно усложняющихся SPA-приложений. Приложения все активнее превращались в толстые клиенты а проблемы толстых клиентов требуют толстых решений, а не каких-то гипермедийных клиентских приблуд.
А затем вышел GraphQL – и тогда словно плотину прорвало.
GraphQL – это образцовый не-REST: вам никак не обойтись без документации, если нужно понять, как работать с API, использующим. Здесь связь между клиентом и сервером чрезвычайно сильная. В GraphQL нет нативных гипермедийных элементов управления. GraphQL предлагает схемы и во многом ощущается как обновленная и урезанная версия XML-RPC.
И я вам скажу – это нормально!
Во многих ситуациях GraphQL людям действительно по душе и, если вы пишете приложение в стиле толстого клиента, следующий тезис глубоко верен:
Короче говоря, HATEOAS не слишком хорошо подходит для большинства практических ситуаций, в которых используются современные API. Вот почему за почти двадцатилетнюю историю HATEOAS так и не получил широкого признания среди разработчиков. С другой стороны, GraphQL распространяется молниеносно, так как действительно решает проблемы.
Итак, GraphQL – это не REST, он не претендует на звание REST и не собирается быть REST.
Но на сегодняшний день абсолютное большинство компаний и разработчиков, даже те, кто с энтузиазмом добавляют функционал GraphQL в свои API, продолжают называть свои творения словом «REST».
Итак, что же нам теперь с этим делать?
Как ни жаль, voidfunc, вероятно, прав:
Можете сколько угодно тыкать пальцем в определение REST, но эта битва давно проиграна. Сегодня REST – это общеизвестный термин, под которым понимается комбинация HTTP+JSON RPC.
Мы и дальше будем называть REST такие JSON API, которые REST определенно не являются, просто потому, что теперь их так все называют.
Несмотря на то, что я все активнее тыкаю пальцем в определение REST, выведенное черным по белому, даже через 50 лет некая Global Omni Corp. будет зазывать людей на разработку 138-й версии swagger-документации по их корпоративному RESTful JSON API.
Ситуация безнадежна, но не слишком серьезна.
Как бы то ни было, здесь мне представилась возможность объяснить новому поколению веб-разработчиков, что такое REST и, в частности, единый интерфейс. Может быть, они никогда и не встречали этих концепций в их исходном контексте и полагают, что REST === JSON APIs.
Люди чувствуют, что здесь что-то не так и, может быть, REST, реальный, настоящий REST, а не псевдоREST, отчасти позволяет ответить на их вопросы.
Как минимум, идеи, положенные в основу REST, достаточно интересны и их стоит знать для общего развития любому программисту.
Здесь вырисовывается и более крупный мета-тезис: даже сообщество относительно неглупых людей (веб-разработчики первой волны), к услугам которых был Интернет и весьма четкая (пусть и местами академичная) дефиниция термина REST, не смогло удержать значение REST в его исходных рамках. Чтобы значение REST полностью изменилось, потребовалось два десятилетия.
Если можно настолько превратно понимать очевидные вещи, то в чем еще мы можем настолько же заблуждаться?
Комментарии (14)
Zenitchik
29.07.2022 13:09+10Что толку от того, что отклик описывает сам себя, если это описание не машиночитаемое?
ProstakovAlexey
29.07.2022 13:20+1Когда это писалось, были другие задачи - показать ответ человеку, а он разберется. Сейчас ситуация изменилась - ответ нужен JS (или другой программе), а она уже покажет что-то человеку. Поэтому и стали требоваться документация, ранее (во времена той дисертации) нужно было следовать html стандарту, не более. Сейчас таких сайтов уже нет. И на то есть основания - посмотрите на пример ответа из статьи, элементарная задача покрасить баланс красным если он меньше 1000 потребует изменения бека или эпических костылей с парсингом на стороне клиента
Hardcoin
29.07.2022 15:51+6У человека какая-то травма из-за изменения значения термина. REST-HTML никто делать не будет, это непрактично. Для эффективной разработки необходимо отделение данных от предоставления.
Всё, чего можно добиться - назвать json-api каким-то другим словом, не rest. Автор может придумать такой термин и форсить его, если захочет.
shalamberidze
29.07.2022 18:09+1А как в REST-HTML использовать POST DELETE etc
hapcode
29.07.2022 22:03Тэг
form
поддерживает POST. А остальное можно накостылить путем добавления в форму поля<input type="hidden" value="delete" name="_method">
как в Ruby on Rails, например.
NightShad0w
29.07.2022 20:56+4А вот концептуально, при использовании HATEOAS, как происходит идентификация ресурсов на уровне логики клиента? Все эти популярные примеры показывают, что надо с сервера прислать список связанных ендпоинтов. НО сами-то ендпоинты не машино-документируемы. Как абстрактный движок правильного REST клиента будет выбирать, какой следующий эндпоинт ему нужен? Или это сугубо вопрос в сфере RPC и настоящий REST он по определению заточен на кнопочки для робота из мяса и никакой программной логики на клиенте быть не может.
makssof
30.07.2022 00:22+2Лично для меня подобные статьи из серии "Раньше было лучше". Ну, если и не лучше, то "по-другому".
Да, по-другому. Да, раньше веб был вебом. А теперь покажите мне ресурс, который агрегирует лишь с браузером пользователя и только для рюшечек в браузере создается. Мир меняется, подходы меняются, пусть и за основу взяты парадигмы из прошлого. А это не прекрасно ли, когда ребёнок растет? Когда он не просто продолжает быть полезным, он захватывает умы и становится одним из самых ходовых!
Мы программируем не ради программирования. В конечном итоге всё есть продукт. А текущая реальность такова, что продуктом является множество клиентов-потребителей апи. Поэтому на мой взгляд это не "забывание истины REST'а", а его адаптация под стать времени.
Mike_GoodWin
30.07.2022 19:03+1"Раньше летательными аппаратами называли только самолеты с бумажными крыльями и дирижабли, неправильно сегодня называть гиперзвуковые спмолеты летательными средствами-у них крылья металлические и они не дирижабль". Занудство чистой воды.
relgames
31.07.2022 13:03Интересно, как именно автор предлагает отказаться от документации? Ну будет API клиент знать, что есть POST /document/create, а откуда он возьмёт формат данных?
Или типа идея в том, что SPA это плохо, и браузер должен вызвать GET /document/create, и там вернётся форма? Это уже устарело давно.
george3
31.07.2022 15:26-1Если я прально понял проблему - REST+JSON - много головняка, стандартов, писанины. Если закрыть более простым и менее гемморойным протоколом чем html/xml, то возможно, муры писать будем меньше. Такой протокол/решение есть - https://github.com/Claus1/unigui
panzerfaust
Здесь и далее какая-то игра в наперстки. Ничто не мешает включить элементы HATEOAS в JSON, ничто не мешает убрать их из HTML. Все зависит от задач данного апи. В большинстве случаев было бы странно заставлять клиента начинать с "елдиной точки входа" и пробегать через N промежуточных запросов ради получения результата. Поэтому и существуют сваггер и апи доки.
Практика разошлась с описанной 20 лет назад теорией - бывает. А перегрузка и неправильное использование терминов в IT вещь в принципе нередкая.