Комментарии 85
Проблема не в росте числа запросов, проблема в росте числа последовательных запросов. То есть даже мультиплексирование в SPDY/HTTP2 от тормозов не спасает. Если хочется "самодокументированных" API, лучше выдайте наружу RAML-спеку, по которой можно сгенерить клиент.
Три вопроса:
Клиенту все еще нужно догадываться, что запрос на
add-recipe
нужно делать постом а не путом или патчем? Почему бы не указывать метод явно? Например:
"links": { "add-recipe": { "href": "http://example.com/recipes", "method": "POST" }, "recent-recipe": { "href": "http://example.com/recipes/my-recipe", "method": "GET" } }
Почему бы значение 'href' не указывать абсолютной ссылкой? Таким образом можно абстрагироваться от синглтон-домена и мы легко можем заменить домен в случае разнесения функциональности по разным доменам-субдоменам.
А можем и не заменить.
"add-recipe": { "href": "http://example.com/recipes", "method": "POST" }
"add-recipe": { "href": "http://mycompany.example.com/recipes", "method": "POST" }
- Совершенно не понял почему ключом выступает ссылка? (в ваших примерах это "http://acme.com/recipes/rels/you-can-also-like"). Невнимательно прочитал?
Спасибо.
- Тут вопрос дизайна и предпочтений. Этот пример показывал как можно было бы сделать свой, ни на что не похожий hypermedia тип (
application/vnd.com.acme.recipes+json
). У авторов такого типа, в его документации было бы описано как создавать рецепты, какой метод нужен. Дизайн странный, но используя свой Content-Type они вполне это могут сделать, хотя ваш вариант мне нравится больше :).
Даже у авторов generic-типов нет консенсуса на этот счет. В HAL методов нет, в Siren есть. Альтернативой документирования методов, или указания их в ресурсе может являться ссылка на профиль, такой ресурс с мета-информацией. - Вполне допустимы оба варианта, дело вкуса. У нас в проектах для "локальных" ресурсов мы опускаем домен, а для связи нескольких микросервисов уже указываем полный линк. Клиенты поддерживают оба формата, так что для них все прозрачно. Все как со ссылками на веб страницах.
Скорее я не раскрыл этот момент. Если надеть шляпу формалиста и бюрократа, то все "простые" link relation'ы нужно регистрировать в IANA. Эта организация ведет список общеупотребимых идентификаторов, таких как
self
,prev
,next
. Поэтому используя url, мы вводим namespace чтобы точно не пересечься семантически с другими доменами на просторах сети. Следовать этому подходу или нет — зависит от обстоятельств. Есть варианты когда создатели забивают на это, и используют короткие имена, еще один подход использование префиксов, напримерacme:add-recipe
. Тот же HAL подерживает curies раздел, где задается описания этих сокращений, и ресолв до полного url'a. Вот например прим с сайта HAL:
"_links": { "curies": [ { "name": "doc", "href": "http://haltalk.herokuapp.com/docs/{rel}", "templated": true } ], "doc:latest-posts": { "href": "/posts/latest" } }
Но почему-то, чем дальше тем больше это начинает напоминать SOAP.
XPath — уже придумали аналог JsonPath
XMLSchema — Json Schema
валидация, ссылки друг на друга, метаданные и пр.
Даже JOLT вместо XSLT придумали.
После этого хочется спросить — «ну и чем вам угловые скобочки не понравились»?
Мы сейчас получим все это же, только не в виде стройной структуры, которая была в XML-технологиях, а в виде самодеятельного несовместимого зоопарка?
Спасибо за ссылку, посмотрим.
Это кстати характерный пример, когда человек делает обзор имеющихся форматов, ему ничего не нравится и он создает свой. Отличная иллюстрация — https://xkcd.com/927/
API implementation guidance
Одна из лучших статей.
Я не думаю, что вопрос выбора инструмента SOAP или REST определяется сложностью задачи. Кстати, сравнивать конкретную технологию с архитектурным принципом не вполне корректно.
Кстати в отличной книге Майка Амундсена Restful Web APIs упоминается CoAP — протокол для простых электронных устройств, использующий REST принципы. Я не буду развивать эту тему, так как не специалист в этой области, но стоит отметить. что не все упирается в http.
Мне кажется, что обычно клиент решает свою задачу, и стороннее API подключается по принципу «подключил и забыл…». Странно ожидать от него, что он будет следить за рекомендациями.
API бывают разные. Те которые вы, скорее всего, имели ввиду — делаются для решения определенной задачи. Такому клиенту действительно ничего не нужно его захардкодили и все.
Но этим же мир клиентов не ограничивается. Можно заморочиться и сделать более умного клиента или сделать клиента который используется интерактивно. Самый наглядный пример — HAL или Siren браузер который может работать с любым API. Еще пример — робот гугла который ходит по сайтам и распознаёт микроформаты в html разметке.
Если не залезать так высоко в абстракции, вот вам более простой пример: оформление заказа. Инвойс к заказу вы получите только после оплаты. Не гипермедиа вариант — сразу все ресурсы описать в документации, но тогда вам нужно клиенту как-то дать понять есть уже инвойс или нет. А так есть линка invoice
в ресурсе "Заказ" есть и инвойс, нет значит еще не готов.
DELETE нарушает связность, когда пройдя по ссылке можно получить 404.
PUT не дружит с совместным редактированием. Либо дружит, но через дополнительные методы LOCK и UNLOCK.
Так что хороший апи должен поддерживать следующие методы: GET, HEAD, POST, PATCH.
А в некоторых случаях (когда идентификаторы выбираются клиентом) и POST не нужен. Яркий пример — вики.
Пожалейте руки создателей многих API, включая Amazon S3, а также кучи других :)
Если серьезно, то конечно мнение крайне субъективное. Зачем винить молоток что вы ударили себя по пальцам? Если вам нужна параллельная работа и ваши ресурсы не позволяют это сделать, то вопрос скорее к дизайну ресурсов, чем к методам из спецификации.
За использование PUT и DELETE нужно руки отрывать :-)
А в некоторых случаях (когда идентификаторы выбираются клиентом) и POST не нужен
Налицо логическое противоречие: и это вам не нравится и то вам неправильно. Надеюсь вы не GET'ом собираетесь ресурсы создавать?
Если серьезно, то конечно мнение крайне субъективное.Я привёл аргументы.
Зачем винить молоток что вы ударили себя по пальцам?Я использую саморезы и не имею такой проблемы.
Если вам нужна параллельная работа и ваши ресурсы не позволяют это сделать, то вопрос скорее к дизайну ресурсов, чем к методам из спецификации.Правильный дизайн ресурсов не позволяет работать с ними через метод PUT по вышеозначенной причине.
Налицо логическое противоречие: и это вам не нравится и то вам неправильно.Тут нет противоречия.
Касательно POST — попробуйте реализовать API без него и поймёте, как это удобно. Если вкратце:
1. Идентификатор ресурса у вас есть самого начала, что избавляет от необходимости вводить временные идентификаторы до первого сохранения, от которых очень много проблем с клиентской стороны.
2. Все запросы у вас идемпотентные и вы можете без опаски любой из них повторять.
Но это весьма не «традиционный» путь.
Надеюсь вы не GET'ом собираетесь ресурсы создавать?Нет.
Касательно POST — попробуйте реализовать API без него и поймёте, как это удобно.
В своем первом комментарии вы заявили, что PUT и DELETE не использовать (отрывание рук — сомнительное руководство к дейстивю), сейчас заявляете что POST тоже не использовать.
Внимание вопрос — какой метод протокола HTTP вы используете для создания ресурса?
PATCH, очевидно. Например, создание статьи про апи:
PATCH /user=jin/article=api
{ «title»: «API», «description»: "...", «content»: "..." }
200 OK
{ «author»: "/user=jin", «created»: «2016-04-09T18:45:00Z», «updated»: «2016-04-09T18:45:00Z» }
PATCH не идемпотентный метод в широком смысле, в RFC посвященном ему об этом говорится страница 3, второй абзац
В любом случае, удачи вам. Если ваш API решает поставленные перед ним задачи, это прекрасно!
Отличие RPC/SOAP от REST не в сложности/простоте реализации, а в семантике. RPC — это когда прикладная задача моделируется объектами и методами, а REST — это когда задача моделируется ресурсами и отношениями между ними. Эти способы взаимовыразимы друг через друга (REST можно построить на базе RPC и наоборот), но не одно и то же.
Посмотрите RFC 7231, в частности метод POST. Там четко написано, что семантику определяет сам ресурс, который обрабатывает запрос. В связи с чем если у ресурса сложное поведение, через POST можно сделать разные действия. Понимаю что можно разное поведение можно реализовать и через PUT, но у него более четкая семантика с точки зрения HTTP.
Вы еще забыли важный момент в отличии RPC от REST — в RPC все методы определны заранее, это действительно похоже на сигнатуру класса. В REST при помощи гипермедиа контролов (их наличия и отсутсвия) сервер как владелец ресурса определяет что может делать клиент в текущий момент.
Не забыл. Этот момент непринципиален в том различии REST и RPC, о котором я говорил. Но можно его считать целью этих различий (некой практической выгодой архитектуры).
Почитайте пожалуйста первый параграф по ссылке выше. Там простым английским языком написано, что в общем случае семантика POST'а не определена и ресурс может такой запрос обрабатывать как хочет не нарушая спецификацию.
Вы правы что в большинстве своём он применяется для создания чего-либо, но не нужно воспринимать частый, но все же конкретный пример использования за семантику определенную в стандарте.
Ссылочки выше нет :). Вот здесь актуальная спецификация: https://tools.ietf.org/html/rfc7231#section-4.3.3. Вам первый параграф.
REST — это архитектурный принцип, HTTP — протокол. Почитайте внимательно диссертацию Филдинга.
Я согласен что REST это про ресурсы, а не про набор заранее определенных вызовов, как в случае с RPC.
Но я не совсем согласен со следующими моментами:
Семантика методов. В первом комментарии вы сказали что "GET, POST, PUT, PATCH, DELETE строго определена семантика".
- Это так для всего кроме POST. Как я указывал ранее, в актуальной спецификации HTTP у post'а семантику определяет сам ресурс и REST тут ничего не меняет, потому что REST не меняет ограничений протокола.
Я не спорю что много задач можно решить чисто CRUD подходом, вводя самоограничение, что POST мы используем только для создания ресурса, но не стоит обобщать это на все API и на протокол HTTP как таковой. REST, как принцип, нас совсем не ограничивает — он предписывает серверу сообщать клиенту состояние ресурса (и что с ним можно делать) и не нарушать семантику протокола который используется между ними используя его возможности по назначению. Указывать явно или не указывать метод для выполнения действия уже вопрос реализации.
"HAL — язык описания отношений".
- Вообще это формат представления ресурса у которого в спеке четко прописано где находятся ссылки. И форматов подобных ему достаточно много. Отношения описывают линки, семантика которых задаётся relation'ом. Тут эти форматы ничего не изобретают, используя уже известные конструкции из html'я и atom фидов.
- То что там нет методов не истина в последней инстанции, а просто виденье его создателя — об этом Майк Келли лично говорил на конфе API Craft в 2014 году.
2. Не могу оспорить вашего мнения. Оно отвалится самостоятельно, когда (если) примите то, что я описал в первом пункте.
Цитату из диссертации приведете?
Почему вы меняете состояние ресурса созданием нового виртуального ресурса?
REST — он не о действиях, а о состояниях.
PATCH /ракета(123)
target name =London
200 OK
target
name =London
pos
35.2213
12.4367
state =flying
Что-то вас бросает из одной крайности в другую :-)
Да, возможно, слово «например» в моих словах недостаточно чётко указывало, что я привожу лишь один из вариантов реализации. Т.е., если пришла мысль добавить новое действие к ресурсу, то это в REST будет передачей нового состояния — либо «виртуальный» вложенный ресурс, либо «виртуальное» дополнительное поле в ресурсе, но никак не «виртуальная» семантика глагола. Да, ему нужно думать состояниями, а не действиями. Может, такая формулировка кому-то действительно будет удобнее, чтобы испытать просветление. Но, мне казалось, что эту сторону вопроса уже обсудили выше, и затруднения вызывает именно проблема отсутствия глаголов в HAL, отчего и начал развиваться диалог. Безусловно, всегда можно найти, чем мои слова можно дополнить. Не обязательно это делать в форме возражения.
Что примеры нужно приводить корректные, чтобы у читателей не возникало неправильного представления об обсуждаемом вопросе. Типичное заблуждение — переносить глаголы из хттп-метода в урл и использование метода post. Это рест формально, но не по духу.
Не нужно вообще переносить глаголы куда-то, нужно сразу проектировать в духе REST. Декомпозиция на ресурсы зависит от прикладной области и ограничений реализации (что, как и где будет обрабатываться, как контролироваться). И не нужно спорить ради спора (либо сформулируйте, пожалуйста, компактным тезисом суть вашего возражения, т.к. я не понимаю предмета спора).
К сожалению, это массовое заблуждение. Из-за таких вот примеров.
Какое отношение внутренняя реализация (логирование, проверка прав) имеет ко внешнему api? Каким образом ваш клиент должен догадаться, что для изменения состояния ресурса "ракета", необходимо создать ресурс "запустить"? И чем это знание принципиально отличается от знания глагола "запустить"?
Это не внутренняя реализация, а исключительно прикладная задача. Представьте журнал полёта ракеты и необходимость доступа к нему сержантов, но не рядовых. Это не системное логирование поведения программы для программистов или системных администраторов.
Вы хотите сказать, что для запуска ракеты мне необходимо создать ресурс "журнал полёта"? :-)
Попробуйте получить то же самое манипуляциями со «state=flying».
Вы программы на компе тоже запускаете путём создания лог-файлов?
А вы как это делаете — путём изменения у программы статуса «запущено»? Что-то типа «patch notepad.exe status=running»?
Можно назвать этот файл «логом команд», а можно «командным файлом», по сути он и то, и другое, чисто терминологический вопрос (логи обычно read-only)
Ок, вы "командный файл" запускаете созданием "журнала работы"?
Журнал работы выглядит как-то так:
Это содержимое лога. Его создаёт программа (ракета) во время работы сама. А не мы создаём лог файл, чтобы запустить программу (ракету).
Проиграть заново можно лишь идемпотентные запросы.
Если вам до сих пор это не понятно, то мне больше нечего добавить.
Ничто не мешает создать файл с командами, которые были логгированы при предыдущем запуске, и прокрутить эти команды ракете. Или всем ракетам. С воспроизводимым результатом. Лог команд превращается в скрипт одним движением руки, это по сути одно и то же — последовательность команд, прошлых (лог) либо будущих (скрипт). Скрипт можно создать, даже не создавая лога. И мы таки создаём скрипты, чтобы запустить программу. Мне непонятно, что вам _тут_ непонятного и о чём вы спорите?
> Проиграть заново можно лишь идемпотентные запросы.
Я вообще намекал на CQRS и event sourcing, при чём тут идемпотентность?
Клиент догадываться ни о чём не должен, он должен знать, как работать с конкретным API. REST не избавляет от необходимости понимать прикладную область и ресурсы, на которые её декомпозировали в API. Он лишь избавляется от головной боли, связанной с распределением, кешированием, разграничением доступов, интерпретацией ошибок и статусов — ограничения REST этому способствуют (как раз потому, что не позволяют как попало обращаться с глаголами, и потому, что заставляют думать ресурсами, а не действиями).
А всё потому, что тут смешаны истинное состояние сущности, поле её состояния (state="..") и подача команды на изменение состояния. Это и есть типичное заблуждение — считать, что изменение поля состояния эквивалентно изменению истинного состояния сущности. Такое бывает только для самых тривиальных случаев, например, если сущность — это просто какая-то тупая конфигурация без поведения (пользовательский профиль, настройки и т.п.). В менее тривиальных случаях мир становится асинхронным, и эта простая схема перестаёт работать.
Допустим, вы решили запустить ракету — что будете делать? Выставите «state=flying»?
мы посылаем PUT/PATCH, а состояние всё не меняется и не меняется — тоже неудобно.
Вполне нормальная ситуация, когда фактическое изменение состояния отличается от запрошенного. В описанном вами случае будет так:
PATCH /ракета(123)
state =activated
200 OK
state =fuel-loading
А сама ракета — конечный автомат со следующими состояниями: staying => activated -> fuel-loading -> ready -> flying => detonated
Жирные стрелки — переходы, управляемые клиентом. Тонкие — автоматические переходы.
А всё потому, что тут смешаны истинное состояние сущности, поле её состояния (state="..") и подача команды на изменение состояния.
В dataflow архитектурах нет понятия "команды" — все процессы определяются текущим состоянием. Это очень гибкий подход, рекомендую с ним ознакомиться.
В менее тривиальных случаях мир становится асинхронным, и эта простая схема перестаёт работать.
Всё замечательно работает, если мыслить не в терминах событий и действий, а в терминах состояний и их синхронизации.
> В описанном вами случае будет так:
> PATCH /ракета(123)
> state =activated
Почему ваш вариант лучше чем:
POST /missiles/123/military_activity/
command=arm&confirmationCode=CEJBCWJNSD&approvedBy=Pupkin
POST /missiles/123/military_activity/
command=lock&targetId=UWYEG&confirmationCode=JCBEIWIEJNV&approvedBy=Pupkin,Ivanov
POST /missiles/123/military_activity/
command=launch&confirmationCode=IUWIEFBCAMN&approvedBy=Pupkin,Ivanov,Putin
Или, вариант попроще (если не нужны подтверждения каждого шага):
POST /launches/
missileId=123&launchpadId=RVMOW&targetId=UWYEG&confirmationCode=IUWIEFBCAMN&approvedBy=Pupkin,Ivanov,Putin
HTTP/1.1 201 Created
Location: /launches/293848
GET /launches/293848
HTTP/1.1 200 OK
{ возвращается состояние ракеты, пусковой площадки, цели, и т.п. }
Как в вашем варианте добавить всю ту пачку дополнительной информации, требующейся для каждого шага запуска? Делать её частью состояния ракеты? Но коды подтверждения пуска, ответственные лица, цели, расчёты, результаты, и т.п. не относятся к ракете, это именно что атрибуты _пуска_. Вполне логично выделить боевую активность в отдельную сущность, и запускать ракеты созданием «пусков». Ракета пусть меняет своё состояние «реактивно».
И это ещё не упомянуты прочие действия с ракетой, влияющие на её состояние — ТО, испытания, учения, транспортировка и т.п. Запихивать всё это в конечный автомат «ракета» — это умаяться можно. Пусть лучше она будет пассивной железякой, которая на земле не имеет своего поведения (тем более управляемого её собственным состоянием), а за переходы пусть отвечают более компетентные сущности.
POST /missiles/123/military_activity/
command=arm&confirmationCode=CEJBCWJNSD&approvedBy=Pupkin
А чем ваш вариант лучше, чем:
ARM /missiles/123/
confirmationCode=CEJBCWJNSD&approvedBy=Pupkin
Оба варианта — RPC.
POST /launches/
missileId=123&launchpadId=RVMOW&targetId=UWYEG&confirmationCode=IUWIEFBCAMN&approvedBy=Pupkin,Ivanov,Putin
А вот тут у вас, наконец, получился REST :-)
Как в вашем варианте добавить всю ту пачку дополнительной информации, требующейся для каждого шага запуска?
Для этого есть http-headers.
Вполне логично выделить боевую активность в отдельную сущность, и запускать ракеты созданием «пусков».
Боевые операции — да. Журналы полёта — нет.
Пусть лучше она будет пассивной железякой, которая на земле не имеет своего поведения (тем более управляемого её собственным состоянием), а за переходы пусть отвечают более компетентные сущности.
Что ж вас так из крайности в крайность-то бросает? Ракета — тоже вполне конкретная сущность, которая что-то умеет, а что-то не умеет. И изменение других сущностей "реактивно" может влиять и на её состояния. И декомпозиция на ресурсы должна происходить в соответствии с предметной областью, а не выдумыванием RPC over REST.
POST /missiles/123/military_activity/ — это нормальный REST. Обычная коллекция сущностей, её можно читать GETом, фильтровать, выбирать отдельные активности и т.п. У каждой активности есть свой постоянный URL, состояние, связанные сущности, и т.п. Они выглядят, как «глаголы», но на самом деле это «существительные» (на самом-самом же деле эта граница весьма условна). Банковские операции — другой классический пример.
> А вот тут у вас, наконец, получился REST :-)
Те же яйца. Просто теперь активности типа «launch» выделены в отдельную коллекцию, с абсолютно тем же интерфейсом. Схема данных построже стала (military_activity полиморфная коллекция).
Разница в том, что launch представляет из себя отдельный бизнес-процесс, имеющий продолжительность и промежуточные состояния, а military_activity — просто операции (зачастую атомарные) над бизнес-объектом и их введение продиктовано ни чем иным как желанием сделать RPC средствами REST.
Я ещё раз подчеркну, что REST — он о состояниях, а не действиях выраженных в форме существительных. И если у вашей ракеты есть состояние state, то и изменять его надо редактируя ракету. А если по бизнесу у вас есть отдельный процесс "launch", то и состояния state у ракеты быть не должно. Зато у "launch будет состояние "stage", которое опять же можно изменять редактированием "launch".
RPC это бы было бы, если бы там были команды типа «POST /missile/123/change_state?state=flying».
> И если у вашей ракеты есть состояние state, то и изменять его надо редактируя ракету.
Как я уже писал, это хорошо работает лишь с простыми состояниями без зависимостей. Как только появляются сложные зависимости, становится выгодным вынести «изменение состояния и его зависимостей» в отдельные workflows, представляя их отдельными ресурсами.
> А если по бизнесу у вас есть отдельный процесс «launch», то и состояния state у ракеты быть не должно.
Это почему же? У ракеты вполне может быть состояние «state=flying» (равно как другие состояния, скажем, «mass», «active_stage», «altitude» и т.п.), зависимое от состояния процесса «launch», и ведомое этим процессом. Делать «mass» состоянием процесса как-то глупо, потому что на массу ракеты могут влиять разные процессы (заправка, например).
О да, ввод целей — это целый процесс.
POST /missile/123/change_state?state=flying
|
POST /missiles/123/military_activity/
command=arm
не вижу принципиальной разницы.
Как я уже писал, это работает и со сложными зависимостями, лаконично инкапсулируя их, и позволяя клиенту лишь декларировать намерения, а не расписывать конкретные действия. Если нам нужно перевезти и перекрасить ракету, то в терминах состояний достаточно указать целевое состояния, а автоматика сама разберётся что в какой последовательности делать, а в терминах действий необходимо последовательно выполнять действия, дожидаясь завершения предыдущего.
Потому что если одни и те же данные у вас будут доступны из разных мест, то периодически вы будете сталкиваться с их рассинхронизацией. Например, запросили launch, там было состояние fuel-loading, потом запросили ракету, а там состояние flying. В результате, в зависимости от того, через какую модель вы обратитесь, вы получите разные состояния.
Вообще-то, да, процесс со множеством контролей.
> не вижу принципиальной разницы.
А если бы было «POST /missiles/123/military_activities/arming», то увидели бы?
> лаконично инкапсулируя их, и позволяя клиенту лишь декларировать намерения, а не расписывать конкретные действия
Я как раз и инкапсулировал намерения и связанные с ними зависимости в отдельные сущности. Это не конкретные действия типа «установить переменную state в значение flying», это высокоуровневые задачи, описанные на предметном языке, бизнес-логика которых спрятана от пользователя, а наружу торчат лишь CRUD-операции «создать задачу; проверить статус; изменить задачу; отменить задачу; получить историю задач», прекрасно ложащиеся на стандартный REST. Вас, похоже, смутило то, что я назвал их «командами». Хотите, называйте из «задачами» или «намерениями» — суть та же.
Есть ли в HATEOAS каноничный способ сообщить о _доступности_ этих стандартных действий, передавая некую авторизационную информацию вместе с отношениями? Конечно, сервер всегда может дать отлуп на неавторизованную операцию, но что если хочется уберечь пользователя и заранее скрыть в UI запрещённые контролы (кнопку «Удалить», например)?
Для этого добавляется ссылка на ресурс с описанием привилегий. Например: http://nin-jin.github.io/harp=1/article=12345.xml
Получение привилегий — частный случай получения связных ресурсов. Есть разные подходы, которые можно комбинировать:
- Грузить данные лишь для тех элементов, что попадают в видимую область.
- Использовать http2/websockets для осуществления множества легковесных запросов.
- Использовать пакетные запросы.
- Использовать специальные языки запросов для выгребания связанных данных.
Ну а конкретно в примере права делятся на группы. Например, permission=article-my — права пользователя на созданные им статьи. Независимо от пользователя и статьи. Таких наборов прав весьма ограниченное число и они замечательно кешируются.
Это всё максимально мощные штуки, покрывающие все возможные варианты использования. Если проект не требует такой мощи, можно взять только идеи или отдельные спецификации (скажем, ограничиться ролями).
Вам не кажется странным передавать информацию и правах не в самом ресурсе, а копипастить вместе с каждой ссылкой на него, а в случае изменения прав доступа к ресурсу — предлагать перевыкачать все ресурсы, на него ссылающиеся?
Что касается «протухающих» прав, то с правами, передаваемыми в состоянии та же история: они точно так же могут протухнуть. Ничего страшного: будет ошибка на сервере и последующий рефреш.
Стандарта нет, все зависит от дизайна API и задач стоящих перед API. С точки зрения стандартных операций, есть OPTIONS метод, ответ на который должен содержать список доступных HTTP методов которые можно выполнить над ресурсом. В некоторых случаях это может помочь, но если у вас логика сложная то это может быть слишком "грубо" — например вы хотите позволить клиенту делать PUT запрос с определенными данными, но запрещать передавать другие данные. Как вы понимаете, через OPTIONS этого будет сложно достичь.
Могу сказать о своем опыте: если речь идет о "единичных" ресурсах, то отсутсвие ресурса выражается в отсутсвии ссылки на него. С коллекциями мы считаем что они всегда есть, но в случае чего пустые.
Action'ы мы выставляем в основном в тех ресурсах, к которым они относятся, но бывают и исключения. Из последних примеров — есть ресурс asset — содержащий мета-информацию и у него есть action для загрузки бинарных данных для него, естественно указывающий на другой URI. Так же и с созданием элементов для коллекций — action всегда присутсвует в самом ресурсе-коллекции, но в некоторых случаях мы выставляем его в другом ресурсе, если это лучше соотносится с задачами (производительность, логичность с точки зрения семантики ресурса и т.д.).
Спасибо.
Композиция ресурсов — на совести клиента. Т.е., в общем случае REST-приложение — это набор RESTfull API и отдельно хостящиеся клиенты (SPA, мобильное приложение, т.д.), каждый из которых сам реализует композицию данных по-своему. Микросерверность в REST — это возможность клиента работать сразу с кучей разнородных API на разных урлах. Темплейты на сервере не рендерятся, это задача клиента, сервисы предоставляют только данные (ресурсы). Т.е., да, теймплейтами рулит FE, а API рулит BE.
Не надо пожалуйста в таком категоричном тоне про темплейты. В API Craft мейл листе люди обсуждают реализацию API с использованием html, ведь он имеет все для этого необходимое.
Нестандартно, но не запрещено :). Другое дело, что клиентов под это дело мало, но если кто реализует такое API оно будет полностью соответсвовать REST подходу.
Но на практике, мне кажется, это будет выглядеть не так радужно.
Предположим, передо мной стоит задача: получить ресурс по известному идентификатору. Что для меня, как разработчика, проще: сформировать Uri books/123 или полезть в документацию, прочитать, как организован поиск и построить Uri books?searchField=Id&searchText=123? Это ещё хорошо, если поиск реализован. А если нет?
Боюсь, большинство разработчиков предпочтут первый вариант.
Hypermedia — то без чего ваше API не совсем REST