Несколько идей, связанных с REST, конфликтуют в моей голове, когда я пытаюсь реализовать их.
У меня есть серверная система API REST-ful с бизнес-логикой и веб-приложение с пользовательским интерфейсом. Из различных ресурсов о REST (в частности, REST на практике: гипермедиа и системная архитектура ) я знаю, что я не должен предоставлять необработанные идентификаторы своих сущностей, а скорее возвращать гиперссылки с rel="self"
.
Рассмотрим пример. REST API имеет ресурс, который возвращает человека:
<Person>
<Links>
<Link rel="self" href="http://my.rest.api/api/person/1234"/>
</Links>
<Pets>
<Link rel="pet" href="http://my.rest.api/api/pet/678"/>
</Pets>
</Person>
Проблема возникает с веб-приложением. Предположим, он возвращает страницу, которая содержит гиперссылку на браузеры:
<body class="person">
<p>
<a href="http://my.web.app/pet/???????" />
</p>
</body>
Что я должен положить в href
атрибут? Как сохранить URL-адрес сущности API в веб-приложении, чтобы иметь возможность получить сущность, когда пользователь открывает целевую страницу?
Требования кажутся противоречивыми:
- Гиперссылка
href
должна вести к веб-приложению, потому что это система, в которой размещен пользовательский интерфейс. - Он
href
должен иметь некоторый идентификатор объекта, поскольку веб-приложение должно иметь возможность обращаться к объекту при открытии целевой страницы. - В упомянутой книге сказано, что веб-приложение не должно анализировать / создавать URL-адреса REST.
URI должны быть непрозрачными для потребителей. Только издатель URI знает, как его интерпретировать и сопоставить с ресурсом.
Таким образом, я не могу просто взять 1234
URL-адрес ответа API, потому что как клиент RESTful я должен относиться к нему как к чему-то вроде этого http://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0j
. С другой стороны, я должен дать некоторый URL, который ведет к моему веб-приложению, и этого достаточно для того, чтобы приложение каким-то образом восстановило исходный URL API и использовало этот URL для доступа к ресурсам API.
Возможно, самый простой способ - просто использовать URL-адреса API ресурсов в качестве их строковых идентификаторов. Но URL-адреса веб-страниц http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234
выглядят ужасно.
Все это кажется довольно простым для настольного приложения или одностраничного приложения javascript. Поскольку они живут непрерывно, они могут просто хранить URL-адреса в памяти вместе с объектами службы на время жизни приложения и использовать их при необходимости.
С помощью веб-приложения я могу представить несколько подходов, но все они кажутся странными:
- Замените хост в URL-адресах API и сохраните только результат. Огромным недостатком является то, что веб-приложению требуется обрабатывать любые URL-адреса, которые генерирует API, что означает чудовищную связь. Более того, это снова не RESTful, потому что мое веб-приложение начинает интерпретировать URL-адреса.
- Предоставьте необработанные идентификаторы в REST API вместе со ссылками, используйте их для создания URL-адресов веб-приложений, а затем используйте идентификаторы на сервере веб-приложений для поиска необходимых ресурсов в API. Это лучше, но повлияет на производительность сервера веб-приложений, поскольку веб-приложению придется проходить навигацию службы REST, выдавая цепочку запросов get-by-id некоторой формы для обработки любого запроса из браузера. Для несколько вложенного ресурса это может быть дорогостоящим.
- Сохраните все
self
URL-адреса, возвращаемые API, в постоянном (DB?) Сопоставлении на сервере веб-приложений. Создайте для них несколько идентификаторов, используйте их для создания URL-адресов страниц веб-приложения и получения URL-адресов ресурсов службы REST. Т.е. я хранюhttp://my.rest.api/pet/678
URL где-то с новым ключом, скажем3
, и генерирую URL веб-страницы какhttp://my.web.app/pet/3
. Это похоже на реализацию HTTP-кэша. Я не знаю почему, но мне это кажется странным.
Или все это означает, что RESTful API не могут служить бэкэндами для веб-приложений?
источник
Ответы:
Отредактировано с учетом обновлений вопроса, предыдущий ответ удален
Просматривая ваши изменения в вашем вопросе, я думаю, что понимаю проблему, с которой вы сталкиваетесь немного больше. Поскольку в ваших ресурсах нет поля, которое является идентификатором (просто ссылка), вы не можете ссылаться на этот конкретный ресурс в вашем графическом интерфейсе (т. Е. Ссылку на страницу, описывающую конкретного питомца).
Первое, что нужно определить, это то, имеет ли домашнее животное смысл без хозяина. Если у нас может быть домашнее животное без какого-либо владельца, то я бы сказал, что нам нужно какое-то уникальное свойство домашнего животного, которое мы можем использовать для ссылки на него. Я не верю, что это нарушит непосредственное раскрытие идентификатора, поскольку фактический идентификатор ресурса все еще будет скрыт в ссылке, которую клиент REST не будет анализировать. Учитывая это, наш ресурс для домашних животных может выглядеть так:
Теперь мы можем обновить имя этого питомца с Spot на Fido без необходимости связываться с какими-либо фактически идентификаторами ресурсов в приложении. Точно так же мы можем ссылаться на этого питомца в нашем графическом интерфейсе с помощью чего-то вроде:
Если домашнее животное не имеет смысла без владельца (или домашние животные не допускаются в системе без владельца), то мы можем использовать владельца как часть «личности» домашнего животного в системе:
Одно небольшое замечание: если и домашние животные, и люди могут существовать отдельно друг от друга, я бы не сделал точку входа для API ресурсом «Люди». Вместо этого я хотел бы создать более общий ресурс, который будет содержать ссылку на людей и домашних животных. Он может вернуть ресурс, который выглядит следующим образом:
Поэтому, зная только первую точку входа в API и не обрабатывая ни один из URL-адресов для определения системных идентификаторов, мы можем сделать что-то вроде этого:
Пользователь заходит в приложение. Клиент REST получает доступ ко всему списку доступных людям ресурсов, которые могут выглядеть следующим образом:
Графический интерфейс будет циклически проходить через каждый ресурс и распечатывать элемент списка для каждого человека, используя UniqueName в качестве «id»:
При этом он также может обработать каждую найденную ссылку с относительным значением «pet» и получить ресурс «pet», такой как:
Используя это, он может распечатать ссылку, такую как:
или
Если мы пойдем по первой ссылке и предположим, что наш ресурс входа имеет ссылку с отношением «домашние животные», поток управления будет выглядеть примерно так в GUI:
Использование второй ссылки будет аналогичной цепочкой событий, за исключением того, что People является точкой входа в API, и мы сначала получим список всех людей в системе, найдем того, который соответствует, а затем найдем всех домашних животных, которые принадлежат этому человеку (снова используя тег rel) и найдите тот, который называется Spot, чтобы мы могли отобразить конкретную информацию, связанную с ним.
источник
rel
s для выбора ссылок, но не должны предполагать, что знают структуру URL. REST утверждает, что API может изменять URL-адреса по своему усмотрению при условии, что ониrel
остаются прежними. Парсинг URL-адресов приближает нас к SOAP, а не к REST.Я задаюсь вопросом, стоит ли проводить различие между REST API и веб-приложением. Ваш «веб - приложение» должно быть просто альтернативные (HTML) представления одних и тех же ресурсов - что сказать, я не понимаю , как и почему вы ожидаете доступа
http://my.rest.api/...
и ,http://my.web.app/...
и что они будут одновременно одинаковые и разные.В этом случае ваш «клиент» - это браузер, который понимает HTML и JavaScript. То есть веб - приложение , на мой взгляд. Теперь вы можете не согласиться и подумать, что вы получаете доступ к указанному веб-приложению с помощью foo.com и выставляете все остальное с помощью api.foo.com - но вы должны спросить, как foo.com предоставил мне представление ресурса? «Бэкэнд» foo.com прекрасно понимает, как находить ресурсы на api.foo.com. Ваше веб-приложение просто стало прокси - ничем не отличается от того, что вы говорили с другим API (от кого-то другого) все вместе.
Таким образом, ваш вопрос можно обобщить так: «Как я могу описать ресурсы, используя мои собственные URI, которые существуют в других системах?» что тривиально, если учесть, что не клиент (HTML / JavaScript) должен понимать, как это сделать, а сервер. Если вы согласны с моим первым вызовом, то вы можете просто думать о своем веб-приложении как об отдельном REST API, который передает или делегирует другой REST API.
Поэтому, когда ваш клиент обращается к
my.web.app/pets/1
нему, он знает, как представить интерфейс pet, потому что либо это то, что было возвращено шаблоном на стороне сервера, либо, если это асинхронный запрос для какого-либо другого представления (например, JSON или XML), заголовок типа содержимого сообщает ему об этом. ,Сервер, обеспечивающий это, отвечает за понимание того, что такое домашнее животное и как обнаружить домашнее животное в удаленной системе. Как это сделать, зависит только от вас - вы можете просто взять идентификатор и сгенерировать другой URI, что, по вашему мнению, неуместно, или вы можете иметь собственную базу данных, в которой хранится удаленный URI и прокси-сервер запроса. Сохранение этого URI - это хорошо, это эквивалентно закладкам. Вы будете делать все это только для того, чтобы иметь отдельное доменное имя. Честно говоря, я не знаю, зачем вам это нужно - ваши URI API REST также должны иметь возможность добавлять закладки.
Вы уже подняли большую часть этого в своем вопросе, но я чувствую, что вы сформулировали это таким образом, который действительно не признает, что это практический способ сделать то, что вы хотите сделать (основываясь на том, что я считаю произвольное ограничение - чтобы API и приложение были отдельными). Задавая вопрос о том, не могут ли REST API-интерфейсы быть бэк-эндами для веб-приложений, и предположив, что производительность может стать проблемой, я думаю, что вы сосредоточены на неправильных вещах. Это все равно что сказать, что вы не можете создать Mashup. Это все равно что сказать, что сеть не работает.
источник
my.web.app/pets/3
без разбора URL-адресов REST API»?my.web.app/pets/3
без разбора URL-адреса соответствующего ресурса REST APImy.rest.api/v0/persons/2/pets/3
? Или что я там положу?3
вapp/pets/3
потомуapp/pets/3
непрозрачен, он указывает на то , что ресурс вашего веб - приложение хочет. Если это составное представление нескольких других ресурсов (в других системах - ваш API является одним из них), то вам нужно сохранить гиперссылки на эти системы на сервере веб-приложений, а затем извлечь их, преобразовать их в их представления ( например, JSON или XML), а затем подайте их как часть вашего ответа.board/1
указывает,facebook.com/post/123
иtwitter.com/status/789
- когда вы предоставите представление своей доски, вам придется преобразовать эти URI в представление, с которым вы можете работать. Кеш где надо.Предисловие
В этом ответе конкретно рассматривается вопрос о том, как управлять своей собственной схемой URL-адресов, включая уникальные закладки для URL-адресов для ресурсов, для которых внутренний REST API не предоставляет явно идентификатор, и без интерпретации URL-адресов, предоставляемых API.
Обнаружение требует определенного количества знаний, поэтому вот мой взгляд на сценарий реального мира:
Допустим, нам нужна страница поиска, на
http://my.web.app/person
которой результаты содержат ссылку на страницу сведений для каждого человека. Одна вещь , наш передний конец кода должны знать, чтобы сделать что - нибудь вообще является базовым URL для источника данных REST:http://my.rest.api/api
. Ответ на запрос GET на этот URL может быть:Поскольку мы намереваемся отобразить список людей, мы затем отправляем
GET
запрос href поperson
ссылке href, которая может вернуть:Мы хотим отображать результаты поиска, поэтому мы будем использовать службу поиска, отправив
GET
запрос наsearch
ссылку href, которая может вернуть:Наконец, у нас есть результаты, но как мы можем создать наши внешние URL-адреса?
Давайте отбросим часть, которую мы знаем наверняка: базовый URL API, а остальное используем в качестве идентификатора внешнего интерфейса:
http://my.rest.api/api
http://my.rest.api/api/person/1
/person/1
http://my.web.app
http://my.web.app/person/1
Наши результаты могут выглядеть так:
Когда пользователь переходит по этой внешней ссылке на страницу сведений, по какому URL-адресу мы отправляем
GET
запрос на получение сведений об этой конкретной информацииperson
? Мы знаем наш метод сопоставления внутренних URL-адресов с внешними URL-адресами, поэтому мы просто обращаем его вспять:http://my.web.app/person/1
http://my.web.app
/person/1
http://my.rest.api/api
http://my.rest.api/api/person/1
Если API REST изменяется таким образом, что
person
URL-адрес уже есть,http://my.rest.api/api/different-person-base/person/1
а кто-то ранее уже добавил его в закладкиhttp://my.web.app/person/1
, API-интерфейс REST должен (хотя бы на время) обеспечить обратную совместимость, отвечая на старый URL-адрес перенаправлением на новый. Все сгенерированные внешние ссылки будут автоматически включать новую структуру.Как вы, наверное, заметили, для навигации по API необходимо знать несколько вещей:
person
соотношениеsearch
соотношениеЯ не думаю, что с этим что-то не так; мы не предполагаем конкретную структуру URL в любой момент, поэтому структура URL объекта
http://my.rest.api/api/person/1
может измениться, и пока API обеспечивает обратную совместимость, наш код будет работать.Вы спросили, как наша логика маршрутизации может определить разницу между двумя интерфейсными URL-адресами:
http://my.rest.api/api/person/1
http://my.rest.api/api/pet/3
,Сначала я укажу, что вы использовали базу API в своем комментарии, когда в нашем примере мы используем отдельные базовые URL для интерфейса пользователя и REST API. Я собираюсь продолжить пример, используя отдельные базы, но совместное использование базы не проблема. Мы можем (или должны быть в состоянии) отобразить методы маршрутизации пользовательского интерфейса, используя тип носителя из заголовка Accept запроса.
Что касается маршрутизации на конкретную страницу сведений, мы не можем дифференцировать эти два URL-адреса, если мы строго избегаем каких-либо знаний о структуре
self
URL-адреса, предоставляемой API (т. Е. Непрозрачного идентификатора строки). Чтобы сделать это, давайте включим еще одну из наших известных частей информации - тип сущности, с которым мы работаем - в наши внешние URL-адреса.Ранее наши внешние URL-адреса были в формате:
${UI base}/${opaque string id}
Новый формат может быть:
${UI base}/${entity type}/${opaque string id}
Таким образом, используя
/person/1
пример, мы в конечном итогеhttp://my.web.app/person/person/1
.С этим форматом будет работать наша логика маршрутизации пользовательского интерфейса
/person/person/1
, и, зная, что первый токен в строке был вставлен нами, мы можем извлечь его и направить на соответствующую (подробно описанную в этом примере) страницу подробностей на основе этого. Если вы чувствуете себя неловко из-за этого URL, мы могли бы добавить туда немного больше; может быть:http://my.web.app/person/detail/person/1
В этом случае мы разбираем
/person/detail
для маршрутизации и используем остальное в качестве идентификатора непрозрачной строки.Я предполагаю, что вы имеете в виду, что, поскольку наш сгенерированный интерфейсный URL-адрес содержит часть URL-адреса API, если структура URL-адреса API изменяется без поддержки старой структуры, нам потребуется изменение кода, чтобы преобразовать URL-адрес с закладками в новая версия API URL. Другими словами, если REST API изменяет идентификатор ресурса (непрозрачную строку), то мы не можем говорить с сервером об этом ресурсе, используя старый идентификатор. Я не думаю, что мы можем избежать изменения кода в этой ситуации.
Вы можете использовать любую структуру URL, которую вы хотите. В конце концов, доступный для закладки URL-адрес для определенного ресурса должен включать в себя что-то, что вы можете использовать для получения URL-адреса API, однозначно идентифицирующего этот ресурс. Если вы сгенерируете свой собственный идентификатор и кешируете его с помощью URL-адреса API, как в подходе № 3, это будет работать до тех пор, пока кто-то не попытается использовать этот URL-адрес, добавленный в закладки, после того, как эта запись будет удалена из кэша.
Ответ зависит от отношений. В любом случае вам потребуется способ сопоставить интерфейс с URL-адресами API.
источник
person/1
,pet/3
), то как оно узнает, что если браузер открывается,http://my.rest.api/api/person/1
он должен отображать пользовательский интерфейс, и если открываетhttp://my.rest.api/api/pet/3
, то пэт интерфейс?Посмотрим правде в глаза, нет волшебного решения. Вы читали модель зрелости Ричардсона ? Он разделяет зрелость архитектуры REST на 3 уровня: ресурсы, HTTP-глаголы и средства управления гипермедиа.
Это элементы управления гипермедиа. Вы действительно нуждаетесь в этом? Этот подход имеет некоторые очень хорошие преимущества (вы можете прочитать о них здесь ). Но такого понятия, как бесплатное питание, не существует, и вам придется много работать (например, ваше второе решение), если вы хотите получить его.
Это вопрос баланса - вы хотите пожертвовать производительностью (и сделать свой код более сложным), но получить систему, которая будет более гибкой? Или вы предпочитаете делать вещи быстрее и проще, но платите позже, когда вносите изменения в свой API / модель?
Как человек, который разработал подобную систему (уровень бизнес-логики, веб-уровень и веб-клиенты), я выбрал второй вариант. Поскольку моя группа разработала все уровни, мы решили, что лучше иметь некоторую связь (сообщая веб-уровню об идентификаторах сущностей и создании URL-адресов) и взамен получить код, который является более простым. Обратная совместимость также не была актуальна в нашем случае.
Если бы веб-приложение было разработано сторонней организацией или если была проблема с обратной совместимостью, мы могли бы выбрать другой вариант, потому что тогда было бы очень важно иметь возможность изменять структуру URL без изменения веб-приложения. Достаточно, чтобы оправдать усложнение кода.
Я думаю, это означает, что вам не нужно создавать идеальную реализацию REST. Вы можете использовать свое второе решение, или предоставить идентификаторы сущностей или, возможно, передать api url . Это нормально, пока вы понимаете последствия и компромиссы.
источник
Я думаю, что если вы придерживаетесь чего-то похожего на то, что
Atom Syndication Format
вы хорошиЗдесь метаданные, описывающие представляемую запись, могут быть указаны с использованием дополнительных элементов / атрибутов:
Согласно [RFC4287] , содержит URI, который однозначно идентифицирует запись
Согласно [RFC4287] этот элемент является необязательным. Если он включен, он содержит URI, который клиент должен использовать для получения записи.
Это всего лишь два моих цента.
источник
Не беспокойтесь об URL, беспокойтесь о типах медиа.
Глянь сюда (в частности, третий пункт).
В случае типичного веб-приложения клиентом является человек ; браузер просто агент .
Так якорный тег, как
соответствует чему-то вроде
URL-адрес по-прежнему непрозрачен для пользователя, все, что его волнует, это типы носителей (например,
text/html, application/pdf, application/flv, video/x-flv, image/jpeg, image/funny-cat-picture etc
). Описательный текст, содержащийся в якоре (и в атрибуте title), - это просто способ расширить тип отношений таким образом, чтобы он был понятен людям.Причина, по которой вы хотите, чтобы URI был непрозрачным для клиентов, заключается в том, чтобы уменьшить связывание (одна из основных целей REST). Сервер может изменить / реорганизовать URI, не влияя на клиента (при условии, что у вас есть хорошая политика кэширования - что может означать отсутствие кэширования вообще).
В итоге
Просто убедитесь, что клиент (человек или машина) заботится о типах медиа и отношениях, а не об URL, и все будет в порядке.
источник
Я думаю, что вы правы, это самый простой способ. Вы можете релятивизировать URL-адреса,
http://my.rest.api/api
чтобы сделать их менее уродливыми:Если URL, предоставленный API, не соответствует этой базе, он ухудшается до ужасной формы:
Чтобы продвинуться дальше, просмотрите ответ от API-сервера, чтобы определить, какой тип представления вы хотите представить, и прекратите кодировать разделитель и двоеточия сегмента пути:
источник