Как стать автором
Обновить

Комментарии 66

интерфейсы взаимодействия должны быть стандартизированы;
[...]
очень сложно представить себе систему, в которой не было бы хоть какого-нибудь стандартизованного интерфейса взаимодействия, иначе её просто невозможно будет разрабатывать;

В REST интерфейсы не должны быть "просто стандартизированными". Ограничение Uniform Interface (является центральным) отсылает нас к четырём интерфейсным ограничениям: REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state. Ничего не напоминает?


Ключевой вывод, который следует из определения REST по Филдингу, вообще-то, таков: любое сетевое ПО в мире соответствует принципам REST, за очень-очень редкими исключениями.

Я не понимаю, каким образом этот вывод следует.


Всё, на этом определение REST заканчивается. Дальше Филдинг конкретизирует некоторые аспекты имплементации систем в указанных ограничениях, но все они точно так же являются совершенно абстрактными. Буквально: «ключевая информационная абстракция в REST — ресурс; любая информация, которой можно дать наименование, может быть ресурсом».

  1. Паттерны проектирования тоже являются абстрактными, а REST, как архитектурный стиль, стоит "на ступеньку выше": он еще более абстрактный, так как описывает стиль архитектуры приложения в целом, а не отдельную его конструкцию.
  2. Спецификации URI и HTTP определяют понятие ресурса точно так же. Предполагается, что ресурс сам может инструктировать клиентов о том, как этим ресурсом можно манипулировать (manipulation of resources through representations) и в какие следующие состояния клиент может перейти (hypermedia as the engine of application state). Ресурс, отдающий HTML-страницу с формами и ссылками работает в точности так.
  3. Кроме собственно REST, в работе Филднга есть следующий раздел Experience and Evaluation, который описывает его историю и предпосылки его возникновения.
    https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm

наконец, code-on-demand вообще лукавое требование, поскольку всегда можно объявить данные, полученные по сети, «инструкциями» на некотором формальном языке, а код клиента — их интерпретатором.
[...]
Например, очевидно, что требование code-on-demand противоречит требованию независимости клиента и сервера — клиент должен уметь интерпретировать код с сервера, написанный на вполне конкретном языке.

Тег script является эталонным примером code-on-demand. Клиент может затребовать дополнительный код того типа, который в состоянии выполнить, а может и не затребовать. Это необязательное ограничение (и в диссертации ясно написано, почему), но в браузерах оно используется очень широко, особенно в последнее время.


Оставляя за скобками тот факт, что Филдинг весьма вольно истолковал свою же диссертацию, просто отметим, что ни одна существующая система в мире не удовлетворяет описанию REST по Филдингу-2008.

За деревьями не видно леса. Гипертекстовый веб как система сам по себе является примером такой архитектуры. Диссертация филдинга не появилась из ниоткуда — он начал описывать REST с 1994 года именно для того, чтобы формализовать основные принципы гипертекстовой сети во время его работы над стандартизацией HTTP и URI. В это же время Филдинг стал ведущим автором этих спецификаций (Тим Бернерс-Ли прекратил работать над HTTP и HTML с 1993 года, занявшись созданием W3C).


This idealized model of the interactions within an overall Web application—what we refer to as the Representational State Transfer (REST) architectural style—became the foundation for the modern Web architecture, providing the guiding principles by which flaws in the existing architecture could be identified and extensions validated prior to deployment.
Roy Fielding, Richard Taylor. Principled Design of the Modern Web Architecture


И я не могу понять тезис о разнице между REST и "REST по Филдингу-2008", который гуляет еще с ваших прошлых постов. Какие именно отличия имеются между "REST-2000" и "REST-2008"?


Как же сделать эти операции правильно с точки зрения REST? Вот так:
// Получение профиля
GET /user/{user_id}
Authorization: Bearer <token>

Каким образом клиент узнает свой user_id?

> В REST интерфейсы не должны быть «просто стандартизированными». Ограничение Uniform Interface (является центральным) отсылает нас к четырём интерфейсным ограничениям: REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state. Ничего не напоминает?

Всё так. Только в разделе 5.2, где Филдинг обещает обсудить эти ограничения, ничего похожего не обсуждается, только повторяются те же самые слова. Трактовать понятия «самоописываемые сообщения» или «гипермедиа как движок состояния приложения» читатель должен сам. Я, впрочем, об этом специально написал: «Дальше Филдинг конкретизирует некоторые аспекты имплементации систем в указанных ограничениях, но все они точно так же являются совершенно абстрактными.»
> И я не могу понять тезис о разнице между REST и «REST по Филдингу-2008», который гуляет еще с ваших прошлых постов. Какие именно отличия имеются между «REST-2000» и «REST-2008»?

Я в общем-то приложил все усилия, какие смог, чтобы объяснить. REST-2000 оперирует абстрактными ограничениями, типа Hypermedia as an engine of application state, которые можно при желании как объявить выполненными всегда, так и не выполнимыми никогда. Из этого родилось «популярное» толкование REST, как его можно прочитать в многочисленных статьях про REST на Хабре. REST-2008 заявляет, что существует единственно верная интерпретация REST-2000, причём эта интерпретация гораздо ближе к «невыполнима никогда», чем к «выполнена всегда».

"Популярное толкование" как раз-таки родилось у тех, кто эту диссертацию вряд ли открывал. Большинство материалов по REST не ссылаются вообще ни на что, используя термин REST сугубо как модный баззворд.


Все-таки, вы можете привести конкретные отличия или противоречия?

Ну, например:

Филдинг-2008: A REST API should not contain any changes to the communication protocols aside from filling-out or fixing the details of underspecified bits of standard protocols

Филдинг-2000: ничего подобного в ограничениях не написано; более того, в разделе 5.1.4 Филдинг пишет «extensions to the protocols were needed in order for them to communicate reliably. The following sections describe the constraints added to the Web's architectural style in order to guide the extensions that form the modern Web architecture», т.е. явно указывает, что расширения протоколов формируют современный веб и должны следовать той же парадигме REST.
> Каким образом клиент узнает свой user_id?

Выполнив запрос `POST /me` (`POST /startup`, `POST /init`, не суть) перед началом работы с API.

Зачем использовать POST для чтения, если для этого есть безопасный, идемпотентный GET? И чем принципиально это отличается от GET /me?

Попробуйте сформулировать, что за ресурс me, что должна возвращать операция GET и как это соотносится со stateless-принципом.

Мы не меняем предусловия, /me возвращает авторизованный аккаунт исходя из переданного токена. Вопрос остается в силе. Почему POST?

Попробуйте сформулировать, что за ресурс me, что должна возвращать операция GET и как это соотносится со stateless-принципом.

Мы сейчас на второй круг зайдем :) Хорошо, я отвечу вашими же словами:


Приведём простой пример. Пусть в нашей системе есть операции получения профиля пользователя и его удаления. Мы можем организовать их разными способами. Например, вот так:
// Получение профиля
GET /me
Cookie: session_id=<идентификатор сессии>

Ресурс /me возвращает профиль пользователя, который сделал этот запрос. Выше вы предложили сделать то же самое, но через POST /me. В чем разница?

В том, что `GET /me` противоречит концепции REST, а `POST /me` нет.

GET возвращает представление некоторого ресурса. В данном случае произойдёт следующее: серверный код найдёт тот ресурс, который вы реально запросили (пользовательский профиль с некоторым id) и вернёт его как представление ресурса `/me`. Получается, что /me не указывает на некоторую «именованную сущность», как предписывает концепция ресурса.

Для устранения этого противоречия нам надо объявить `/me` специальным ресурсом, функцией которого является сопоставление параметров операции (сессии, в данном случае) с профилем пользователя. Объявив `/me` таким ресурсом, мы по определению должны использовать `POST`, поскольку этот метод как раз нужен для того, чтобы эндпойнт обработал приложенные данные в соответствии со своей внутренней семантикой. А `GET /me` должен возвращать его (ресурса-матчера) представление, которого у него обычно нет. Во всяком случае, представление ресурса-матчера — это какая-то информация о самом ресурсе, вряд ли каким-то образом полезная в обсуждаемом контексте.
Получается, что /me не указывает на некоторую «именованную сущность», как предписывает концепция ресурса.

Вряд ли она предписывает. Наоборот, REST определяет "ресурс" как семантику, а не как значение, которое соответствует этой семантике. Давайте просто сошлемся на источники:
REST accomplishes this by defining a resource to be the semantics of what the author intends to identify, rather than the value corresponding to those semantics at the time the reference is created.
https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_2_1


This specification does not limit the scope of what might be a resource; rather, the term “resource” is used in a general sense for whatever might be identified by a URI. […] This specification does not place any limits on the nature of a resource, the reasons why an application might seek to refer to a resource, or the kinds of systems that might use URIs for the sake of identifying resources.
https://tools.ietf.org/html/rfc3986#section-1.1


The target of an HTTP request is called a “resource”. HTTP does not limit the nature of a resource; it merely defines an interface that might be used to interact with resources.
https://tools.ietf.org/html/rfc7231#section-2


Resources are not storage items (or, at least, they aren’t always equivalent to some storage item on the back-end). [...] Likewise, a single resource can be the equivalent of a database stored procedure, with the power to abstract state changes over any number of storage items.
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743


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

Давайте сошлёмся.
Any information that can be named can be a resource: a document or image, a temporal service (e.g. «today's weather in Los Angeles»), a collection of other resources, a non-virtual object (e.g. a person), and so on.

`/me` — это что, по-вашему? Документ или сервис?

Скорее сервис, чем документ. Хотя он может быть ни тем, ни другим.

a temporal service (e.g. «current logged in user») )

Продолжая ваш ход мыслей. Представим ресурс /echo, который в теле ответа отражает запрос обратно клиенту — со всеми заголовками, как есть. Его тоже следует использовать методом POST?

Конечно.

Зачем? Ресурс /echo семантически не предназначен ни для чего другого, кроме извлечения информации; он безопасен; он идемпотентен. Клиент или любой промежуточный агент, зная, что GET идемпотентен, может сам повторить запрос при сбоях связи. А вы предлагаете отбросить эту семантическую информацию, использовав POST. Этой заменой ничего не выиграете.

> Ресурс /echo семантически не предназначен ни для чего другого, кроме извлечения информации

Я же с вами абсолютно не спорю. Я всего лишь говорю, что, обращаясь к нему таким образом, вас интересует не внутреннее представление самого ресурса echo, а результат некоторого алгоритма, на который ссылается /echo. А для этого предназначен POST.

> Этой заменой ничего не выиграете.

Неправда, я выигрываю то, что никакая промежуточная прокси не закэширует результат запроса.
Я же с вами абсолютно не спорю. Я всего лишь говорю, что, обращаясь к нему таким образом, вас интересует не внутреннее представление самого ресурса echo, а результат некоторого алгоритма, на который ссылается /echo. А для этого предназначен POST.

POST, как и GET, тоже возвращает представление, а представление может представлять результат обращения к ресурсу, или представлять состояние, в которое должен перейти клиент. Такого понятия, как "внутреннее представление" (хранимое?) в HTTP нет. Когда мы делаем GET https://habr.com/ru/post/560590/, мы понятия не имеем, возвращает ли этот ресурс "внутреннее представление", результат сложных вычислений или промежуточный ответ от другого сервера. Рандомные цитаты от GPT мы тоже должны через POST получать?) Получить свой адрес по http://ip.changeip.com/ тоже? А считывать метрологические данные https://tgftp.nws.noaa.gov/data/observations/metar/stations/UKLL.TXT тоже через POST? И лицо получать https://thispersondoesnotexist.com/ тоже?


Неправда, я выигрываю то, что никакая промежуточная прокси не закэширует результат запроса.

Я бы просто использовал для этого Cache-Control, а не подменял бы смысл запроса на POST, давая знать, что этот запрос кроме чтения может иметь другие побочные эффекты.

Окей, просто представление. Слово «внутреннее» лишнее.

> Когда мы делаем GET habr.com/ru/post/560590

Ну вот URL мне намекает, что я получу всё-таки какой-то пост под идентификатором 560590, а не копию моего запроса в ответ. На всякий случай уточню, что веб-сайт != API, и принципы его работы чуть другие. Это, кстати, Филдинг отдельно оговаривает

> POST, как и GET, тоже возвращает представление

Ну да. Разница в том, что GET возвращает представление *адресуемой* сущности, а POST не обязательно; он как раз задизайнен чтобы предоставлять доступ к сущностям, не имеющим прямого URL.

> Я бы просто использовал для этого Cache-Control, а не подменял бы смысл запроса на POST, давая знать, что этот ресурс кроме чтения может иметь другие побочные эффекты.

Вы просто говорите, что индицировать одни эффекты (немодифицируемость, идемпотентность) считаете более важными, чем другие (некэшируемость, семантика глагола). Я же говорю, что тут нет общего правила, надо ориентироваться на здравый смысл применительно к конкретной операции.

Запрос «себя» должен (обязан) пробивать кэш, поскольку иначе можно получить доступ к чьим-то чужим данным. С моей точки зрения это важнее, чем индикация идемпотентности. Для операции типа echo — ну, начинается некоторая вкусовщина. Для меня следование семантике важнее, чем потенциальный неперезапрос от прокси.
Кстати, лучше всего ваш `echo` описывается глаголом PUT — что ты в него положил, то и получил.

Это очень странно, потому что /echo априори ничего не записывает и ничего не сохраняет; PUT заменяет представление ресурса данными представленными в теле запроса, но что, если тело запроса будет пустым; кроме этого, ответ PUT тела не имеет.


Как вы себе это представляете? Делать PUT /echo, потом GET /echo? А если один клиент сделает PUT /echo, а другой сделает GET /echo?

Сохраняет в память и возвращает обратно, всё совершенно логично. Куда PUT должен сохранять — стандартом не оговорено. Если кто-то другой обратится к echo, то перезапишет это значение. PUT может иметь тело ответа, конечно.
Сохраняет в память и возвращает обратно, всё совершенно логично.

Да не должен он ничего сохранять, представьте что /echo является stateless по определению. Негде ему это сохранять, и незачем.


PUT может иметь тело ответа, конечно.

Моzilla ошибается? https://developer.mozilla.org/ru/docs/Web/HTTP/Methods/PUT

Мозилла неточна. Я загуглил этот вопрос и попал на ответ от Julian Reschke, одного из авторов спецификации HTTP.


So yes, responses for a succesful PUT can have a reponse body, but normally, it does not make sense to include one. (What for?).
https://stackoverflow.com/questions/63275986/meaning-that-put-method-successful-response-has-body-in-the-mdn-document


Так что мозилловская страничка была недалека от истины. Я тоже не понимаю, какой смысл должно иметь тело в ответе PUT, если в запросе мы все равно обязаны передать полное представление.

Возвращать в ответе представление сущности, которая была модифицирована запросом — стандартная практика для современного фронтенда. Как минимум для того, чтобы клиент знал все значения полей по умолчанию.

Не знаю стандартная это практика для PUT или нет, но PUT совсем не подходит для /echo. Клиенты априори не должы получать эхи, записанные другими клиентами.

Они и не получат, метода GET нет.

Может лучше просто читать через GET, и тогда вас все поймут?)

Мммм, пожалуй, нет.

В теле ответа могут быть:


  • обогащенное мета-информацией тело запроса (created_at, created_by)
  • сообщения об ошибках
  • id транзакции
  • ...
Ну да. Разница в том, что GET возвращает представление адресуемой сущности, а POST не обязательно; он как раз задизайнен чтобы предоставлять доступ к сущностям, не имеющим прямого URL.

Ну, так и /echo вполне является адресумой сущностью с переменным представлением. POST задизайнен для того, чтобы его можно было использовать для любых других операций, не стандартизированных в рамках остальных методов. Как писал Филдинг, "POST serves many useful purposes in HTTP, including the general purpose of “this action isn’t worth standardizing.”"
https://roy.gbiv.com/untangled/2009/it-is-okay-to-use-post


Вы просто говорите, что индицировать одни эффекты (немодифицируемость, идемпотентность) считаете более важными, чем другие (некэшируемость, семантика глагола).

Так HTTP методы ровно для того и предназначены, чтобы случить источником информации об семантике запроса.


Запрос «себя» должен (обязан) пробивать кэш, поскольку иначе можно получить доступ к чьим-то чужим данным. С моей точки зрения это важнее, чем индикация идемпотентности.

Так можно сказать про любой авторизованный GET запрос. Не думаю что вы предлагаете заменять все такие читающие запросы на POST просто чтобы "пробить кеш".


Я же говорю, что тут нет общего правила, надо ориентироваться на здравый смысл применительно к конкретной операции.

Здесь я согласен :)

> Так можно сказать про любой авторизованный GET запрос. Не думаю что вы предлагаете заменять все такие читающие запросы на POST просто чтобы «пробить кеш».

Именно это и скажу. Или у запроса должен появиться уникальный URL, или он должен уйти за POST.

Любым методом )

Со stateless-принципом авторизация в принципе плохо дружит, сам Филдинг это признал в ныне умершей группе рассылки на yahoo:
I should note that the big conflict between REST and security models is the fact that REST does not allow for sessions. What needs to be understood is that sessions are bad for security models too – they cause most of the denial-of-service and man-in-the-middle attacks to be possible. What is needed is an efficient, session free means of authenticating that is more secure than username/password, which is actually an easy problem to solve if you don’t try to solve all of the security problems at once. What is blocking that is the need to negotiate security mechanisms before engaging in secure communication, which is currently done within a session.
https://web.archive.org/web/20191213070235/https://groups.yahoo.com/neo/groups/rest-discuss/conversations/messages/3661

Это в данном контексте совершенно неважно. Даже если предположить, что авторизации в протоколе нет, ну там юзера по IP-адресу идентифицирует, проблема получения своего идентификатора никак от этого не меняется.
В целом же мне не очень понятно, с чем конкретно вы спорите. Сформулируйте, пожалуйста, конкретные тезисы.

Продолжая наш вчерашний флейм. Вы написали, что /users/{user_id} более правильно, чем /me, и далее


// Получение профиля
GET /user/{user_id}
Authorization: Bearer <token>


Теперь URL запроса в точности идентифицирует ресурс, к которому обращаются, поэтому можно организовать кэш и даже заранее наполнить его;
[...]
Наконец, неочевидная польза такого решения заключается в следующем: промежуточный сервер-гейтвей, обрабатывающий запрос, может проверить заголовок Authorization и переслать запрос далее без него (желательно, конечно, по безопасному соединению или хотя бы подписав запрос).

И что


Запрос «себя» должен (обязан) пробивать кэш, поскольку иначе можно получить доступ к чьим-то чужим данным.

  1. Вряд ли гейтвей может переслать запрос серверу без Authorization: A proxy forwarding a request MUST NOT modify any Authorization fields in that request
    https://greenbytes.de/tech/webdav/rfc7235.html#header.authorization


  2. Общий кеш нельзя использовать для ответов на запросы с Authorization: A shared cache MUST NOT use a cached response to a request with an Authorization header field.
    https://greenbytes.de/tech/webdav/rfc7234.html#caching.authenticated.responses



Когда клиент запрашивает собственный профиль, не важно, через /me или /users/{user_id}, обычно он ожидает получить в том числе непубличную информацию об профиле. Такой запрос всегда будет с токеном.

Прокси не может отрезать Authorization, а вот гейтвей ещё как может. Соответственно, кэши за гейтвеем вполне могут существовать.

А гейтвей HTTP-HTTP это не прокси?

Каким образом клиент узнает свой user_id?

Скорее всего он узнает этот URL в процессе авторизации, когда будет создавать токен или сессию. Сервер вернёт ему этот URL в теле ответа, как и завещает делать HATEOAS.
Я именно так и делаю.

Интересная точка зрения и пока лучшая статья, которую я прочитал на habr за неделю, imo
Спасибо. Я потратил на неё очень много времени и усилий ;)

Незря! Очень круто! И дискуссия в каментах пока тоже хороша. ;) Спасибо за труд.

раз можно сделать альтернативную имплементацию сервера — значит, можно сделать и многослойную архитектуру, поставив дополнительный прокси между клиентом и сервером;

Если можно что-то сделать, то это не означает что это "разрешено". "Многослойность" архитектуры должна быть изначально заложена в неё, что бы разработчики сервера и клиента всегда помнили об этом, и соответствующим образом писали код. Иначе они могут сделать не верные предположения (например, что клиент всегда напрямую соединяется с требуемым приложением на сервере), и не корректно реализовать некоторые кейсы.


поскольку клиент представляет собой вычислительную машину, он всегда хранит хоть какое-то состояние и кэширует хоть какие-то данные;

Кешировать ведь может не только клиент, а и любой промежуточный узел. И разработчик клиента и сервера должен про это помнить, и соответствующим образом управлять этим процессом.


Что касается правила на букву S («stateless»), то систем, в которых сервер вообще не хранит никакого контекста клиента в мире вообще практически нет, поскольку ничего полезного для клиента в такой системе сделать нельзя.

Под stateless подразумевается не то, что какая-то из сторон ничего не хранит, а только то, что стороны процесса не должны рассчитывать на то что другая сторона буквально "помит" их предыдущие запросы и принимать на основе этого какие-то решения.
Например клиент не должен ожидать от сервера какой-то особой "памяти" и считать, что раз он только что создал на сервере документ, то он 100% получит его обратно вторым запросом. Как будто сервер — это гардеробщик, который запомнил вас в лицо и выдаст куртку без предъявления номерка, и заодно он ещё волшебник — создаст куртку из воздуха, если её успели украсть (удалили документ).
Это ограничение направлено на возможность простого горизонтального масштабирования сервисов. Что бы не было проблем, если первый запрос клиента обработает первая нода кластера, а второй запрос прилетит на вторую ноду, которая ничего не знает про первый запрос.


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

Это Вы сами додумали и перегнули палку. Нет у Филдинга такого. HATEOAS не требует написание "всемогущего клиента с ИИ". Он нужен только для того, что бы клиент не хардкодил конкретные ссылки, которые требуются для выполнения его задач. Он должен только знать где и как их найти в ответах сервера.
Например как человек на веб-сайте интернет магазина ничего не знает о том, на какую ссылку ему перейти, что бы получить информацию о товаре или добавить его корзину. Но человек знает (ему показали другие люди или он прочитал об этом, в общем у него есть "знание"), что надо искать в тексте особые надписи-ссылки или кнопки, на которых будет написано нужное ему действие (например "Добавить в корзину"). После чего нажимает на них и браузер переходит на нужную страницу или посылает POST запрос на какой-то URL. При этом даже сам браузер не знает смысла этих URL-ов — их сообщил ему сервер. В этом случае человек действует по вполне определённому алгоритму, который очень просто формализовать.
Точно так же и "программный" клиент для API может извлекать из ответов сервера ссылки, нужные ему для выполнения конкретной задачи (которую в него заложили программисты, а не он сам как-то выдумал её по ответам сервера). Для этого просто достаточно дать ссылкам понятное фиксированное название. Для человека это был текст "Добавить в корзину", а для "программы" будет в ответе сервера поле с именем "add_to_cart", а конкретная ссылка будет в значении этого поля.
И как для человека, отсутствие кнопки на странице, означает что он не может выполнить это действие, так и для "программы" отсутствие в ответе ссылки с названием "add_to_cart" может означать, что это действие нельзя выполнить (например товар закончился на складе).
HATEOAS требует только наличие гиперссылок, но не требует что бы клиент как-то мог только по одному тексту ссылки догадываться как её использовать, каким методом её вызывать и какие параметры передавать. Всё это может быть либо заложено в клиент. Либо, как в случае с браузерами, ссылка ведёт на другую страницу, где может находится HTML-форма в которой есть чёткие инструкции для браузера куда и какие данные надо отправлять. Или на другой странице может быть встроен code-on-demand, который выполнит требуемое действие.
Т.е. всё это не является чем-то не реализуемым и волшебным как "всемогущий ИИ".


Как-то вы вскользь упомянули про HATEOAS в начале статьи, хотя Филдинг-2008 чётко написал в конце заметки, что "hypertext constraint" это наиболее важная штука. И если у вас его нет, то не используйте слово REST, придумайте что-то другое.


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

HATEOAS как раз избавляет от проблемы с изменчивостью отношений между сущностями, т.к. клиентам не надо знать как на уровне URL соотносятся разные ресурсы. Ему надо просто получать все URL-ы из ответов сервера, а не строить их самому.


О метапрограммировании и REST по Филдингу

Про это я уже выше написал. Нет ни какой необходимости в сильных ИИ, что бы система могла соответствовать термину REST. Все задачи по прежнему можно решать с помощью умных людишек. Для начала они могут написать клиент, который умеет выполнять конкретные задачи не зашивая у себя в коде полные ссылки на все API-методы. А если потребуется налету добавлять в него новый функционал, то это делается с помощью code-on-demand. Людишки пишут код, а сервер этот код передаёт клиенту для выполнения.
Или вы считаете современные браузеры вершиной развития машин, и они умеют сами себя расширять без привлечения программистов? А ведь браузер — это наверное один из самых лучших клиентов, которому не стыдно повесить ярлык "REST compatible". Ну или правильнее этот ярлык повесить на HTTP + HTML&Co.

> Если можно что-то сделать, то это не означает что это «разрешено». «Многослойность» архитектуры должна быть изначально заложена в неё,

Пойнт следующий: *нельзя* не заложить многослойность, её всегда можно сделать, среверинижирив сервер.

> что бы разработчики сервера и клиента всегда помнили об этом, и соответствующим образом писали код.

Во-первых, разработчики клиента об этом думать не должны.
А во-вторых, интересный вопрос как раз и заключается в том, а что заложить-то. Каким образом код писать?
> Кешировать ведь может не только клиент, а и любой промежуточный узел. И разработчик клиента и сервера должен про это помнить, и соответствующим образом управлять этим процессом.

Снова пойнт в том, что *любая* система хоть как-то кэширует данные, нельзя задизайнить её иначе.

Можно не предусмотреть в дизайне способа управления этим кешированием с обеих сторон.

А этого REST по Филдингу этого как раз не требует. Там написано следующее: «Cache constraints require that the data within a response to a request be implicitly or explicitly labeled as cacheable or non-cacheable.»
Что такое «implicitly labeled» читателю предлагается проработать самостоятельно.

Ну там много над чем надо работать, т.к. это всего лишь принципы. И они даже не для непосредственно разработки системы, а для его архитектуры.
Например в HTTP таким "implicitly labeled" могут быть определённые статусы ответа.
RFC 7231


Responses with status codes that are defined as cacheable by default
(e.g., 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501 in
this specification) can be reused by a cache with heuristic
expiration unless otherwise indicated by the method definition or
explicit cache controls [RFC7234]; all other status codes are not
cacheable by default.
Вновь замечу, пойнт не в этом, а в том, что можно объявить любую систему соответствующей REST. Потому что «implicitly labeled» можно почти что угодно назвать.

Но тем не менее — это надо назвать. Т.е. надо написать доку для системы, в которой будет указано: "Вот это и это — считать кешируемым по умолчанию".


Если в доке для системы нет ни каких указаний по поводу того как работает кеширование, то можно воображать что угодно. Лучше такую архитектуру не причислять к "REST".

> Под stateless подразумевается не то, что какая-то из сторон ничего не хранит, а только то, что стороны процесса не должны рассчитывать на то что другая сторона буквально «помит» их предыдущие запросы и принимать на основе этого какие-то решения.

Предыдущие запросы это растяжимое понятие. Любые данные, которые система хранит, есть результат предыдущих запросов (особенно если это какой-нибудь PUT, семантика которого явна говорит: положи вот это как есть по такому-то адресу). Если сервер не хранит вообще никаких данных о клиенте, то почти никакой полезный сервис на этом не построишь.

> Например клиент не должен ожидать от сервера какой-то особой «памяти» и считать, что раз он только что создал на сервере документ, то он 100% получит его обратно вторым запросом. Как будто сервер — это гардеробщик, который запомнил вас в лицо и выдаст куртку без предъявления номерка, и заодно он ещё волшебник — создаст куртку из воздуха, если её успели украсть (удалили документ).

Это как раз никакого отношения к REST не имеет. Клиент вправе ожидать то, что оговорено контрактом, в том числе сильной консистентности и неизменности «своих» данных, если таков контракт.

> Это ограничение направлено на возможность простого горизонтального масштабирования сервисов. Что бы не было проблем, если первый запрос клиента обработает первая нода кластера, а второй запрос прилетит на вторую ноду, которая ничего не знает про первый запрос.

Да, я не спорю и ниже это оговариваю. Мой пойнт только в том, что принцип сформулирован максимально общо и допускает совсем другие толкования.
Клиент вправе ожидать то, что оговорено контрактом, в том числе сильной консистентности и неизменности «своих» данных, если таков контракт.

В распределённой системе очень резко ограничиваются какие-то "права" и "ожидания", особенно у клиентов. Клиент не один в системе, их таких там много и любой из них может что угодно сделать с данными на сервере, и сервер не должен как-то пытаться это разрулить. А если попытаться это предусмотреть, то система может получится не очень распределённой и не очень быстрой.


Поэтому принципы REST и называются "ограничениями". Ограничить надо в том числе и ожидания, если хотим получить масштабируемую систему.

Тем не менее, ни о каких ожиданиях сильной/слабой консистентности в REST нет ни слова.
И с точки зрения общего архитектурного дизайна никак не могу с вами согласиться. Eventual consistency скорее норма в больших распределённых системах, но это вовсе не значит, что (а) её не нужно явно декларировать, (б) нельзя сделать механизмы для обеспечения контрактов. Версия/ETag ресурса, например, это контракт, с помощью которого и клиент может понять, что произошло, и сервер может нивелировать проблемы. Например:
1. Клиент перезаписывает PUT /entity/{id}
2. Сервер отвечает ревизией ресурса
3. При запросе GET /entity/{id} клиент указывает последнюю известную ему ревизию; сервер проверяет, какой ревизией располагает он, и, если она младше, либо перенаправляет вопрос в мастер, либо отвечает какой-то ошибкой, индицирующей «спроси ещё раз попозже».
Версия/ETag ресурса, например, это контракт, с помощью которого и клиент может понять, что произошло, и сервер может нивелировать проблемы.

Я думаю, что всё это появилось именно из-за ограничения на stateless. Иначе оно могло бы и не понадобиться, если бы сервер был обязан гарантировать клиенту неизменность своего состояния между отдельными запросами.


Тем не менее, ни о каких ожиданиях сильной/слабой консистентности в REST нет ни слова.

Всё что я писал выше выводится из одного только ограничения на stateless. Если клиент ожидает, что на сервере будет какое-то определённое состояние при выполнении очередного запроса — он должен явным образом об этом сообщить в запросе, а не рассчитывать "молча", что ничего не изменилось. В ответ сервер может сообщить клиенту, что не может обработать запрос, т.к. ожидания клиента не соответствуют действительности.

Ну тогда непонятно, чего ж Филдинг возмущался, если любой апп из стора соответствует его правилам.

Для людей, которые наблюдают за работой W3C, в выступлении Филдинга-2008 ничего нового нет. Тим Бернерс-Ли, Филдинг и другие отцы-основатели очень-очень сильно топят за «семантический веб», т.е. требуют, чтобы все данные в мире были размечены и машиночитаемы. Соответственно, очень сильно расстраиваются современному подходу, когда каждый изобретает свой формат API, семантика данных и операций которого описана в доках (т.е. немашиночитаема). Отсюда и филдинговский крик души.
> Это Вы сами додумали и перегнули палку.

Это практически дословная цитата, как она написана самим Филдингом.

A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations. The transitions may be determined (or limited by) the client’s knowledge of media types and resource communication mechanisms, both of which may be improved on-the-fly (e.g., code-on-demand). [Failure here implies that out-of-band information is driving interaction instead of hypertext.]
no prior knowledge beyond the initial URI

Это только про URI, а не вообще про всё на свете, кроме начального URI.


all application state transitions must be driven by client selection of server-provided choices that are present in the received representations

Тут тоже ничего страшного — состояние приложение должно меняться с помощью вариантов, которые предоставляет сервер через свои ответы. Это не говорит, о том что клиент не должен знать где искать и как использовать эти "варианты".
Про это говорится далее:


The transitions may be determined (or limited by) the client’s knowledge of media types and resource communication mechanisms, both of which may be improved on-the-fly (e.g., code-on-demand).

Т.е. клиент может знать о механизмах взаимодействия с ресурсами и/или понимать, что с ними делать в зависимости от их типа. Также он может не знать о чём-то, и будет ограничен в своих возможностях. Например браузер знает как показывать картинки и что делать с HTML, но не знает что делать с ссылкой с незнакомым протоколом. Это заложено программистами в его коде, это та задача ради которой его создавали. Он не развил в себе эту возможность самостоятельно изучая ответы сервера.


Всё это не означает, что клиент должен быть "безвольной пешкой" и абсолютно все свои действия согласовывать с сервером. Это относится только к той части процесса, которая зависит от сервера. Например клиент не сможет без сервера "купить товар", а потому наличие такой кнопки в интерфейсе будет полностью определяться ответом сервера.

По-моему, фраза «клиент должен начинать работу с REST API, не обладая никаким априорным знанием помимо начального URI и набора медиатипов» достаточно однозначно трактуется. Вы можете считать иначе, конечно, ваше право.

Я вполне могу интерпретировать современные мобильные и веб приложения именно как "начать работу не обладая никаким знанием".
В мобилках, используя code-on-demand (установку приложения из "магазина"), мы получаем от сервера код, который расширяет возможности операционной системы и позволяет ей "узнать" как работать с внешним сервисом. В дальнейшем этот "код" продолжает следовать инструкциям сервера, и использовать данные и ссылки полученные от него.
С веб-приложениями это ещё более всё очевидно — юзер вбивает в браузере адрес сайта, и получает готовое приложение, которое меняет своё состояние либо только с помощью пользователя (обычные HTML страницы с формами) или подгружает JS-код и может какие-то действия выполнять "самостоятельно".

> Всё это не означает, что клиент должен быть «безвольной пешкой» и абсолютно все свои действия согласовывать с сервером.

Вопрос не в согласовании действий, а в том, что по Филдингу клиент не должен содержать никакого *кода*, наприсанного специально для работы с REST API, всё должно работать на представлениях медиатипов.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории