REST — поддержка нескольких возможных идентификаторов

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

/users/48573

А теперь похоже

/users/thisisausername.

Единственная проблема заключается в том, что нам все еще нужно каким-то образом получить их через числовые идентификаторы для устаревших потребителей API. Нам не нужны сами URL-адреса REST для перенаправления (например, /users/48573 не должен перенаправлять на /users/thisisausername), нам просто нужен метод для получения правильных данных с использованием старого идентификатора. Решение должно либо предоставлять альтернативный способ доступа к информации о пользователе (который удобно включает новый идентификатор, имя пользователя) по идентификатору, либо доступ только к имени пользователя по идентификатору. Некоторые возможные решения могут быть:

  • Использование узла для указания альтернативного метода идентификации, например. /users/byid/48573
  • Использование параметра запроса для указания альтернативного метода идентификации, например. /users/48573?fetchby=id или /users/48573?byid=true
  • Обработка имени пользователя по идентификатору как другого ресурса, например. /identifiers/username/48573

Какой из них (если есть) ближе всего к правильному REST? Как бы вы справились с проблемой?


person Kelly Ellis    schedule 08.05.2009    source источник
comment
В итоге я реализовал доступ через поля неосновного идентификатора в качестве поиска. Это решение позволяет извлекать несколько типов ресурсов через несколько полей, сохраняя при этом только одно из них в качестве основного идентификатора. В целях согласованности поисковые API возвращают списки. Таким образом, официальный способ доступа к пользователю: /user/thisisausername, а для доступа по идентификатору у нас есть: /users?id=48573 Точно так же мы могли бы искать по ряду различных полей, например: /users?firstName=Kelly Источник вдохновения: jwyseur.blogspot.com/2008/12/ uri-design-for-rest.html (см. поиск ресурса)   -  person Kelly Ellis    schedule 21.07.2009
comment
Итак, вы сделали ставку на кеширование? У меня та же проблема, что и у вас, но я не могу решить их проблему с помощью параметров запроса, которые удаляют одно из основных преимуществ REST API. Мне нравится ваше первое маркированное предложение...   -  person HDave    schedule 06.07.2012


Ответы (5)


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

Лично я планирую использовать префикс сегмента пути, разделенный "=", например, "name=" или "email=":

user/123456
user/name=john.doe
user/[email protected]

Функционально это эквивалентно добавлению сегмента пути (например, «пользователь/имя/john.doe»), но мне кажется, что это более точно соответствует концептуальной модели. Конечно, это незначительная деталь, поскольку RESTful API в любом случае не должны указывать фиксированную структуру URI.

Неиспользование параметров запроса также обеспечивает естественный доступ к подресурсам:

user/name=john.doe/inbox/df87bhJXrg63

Такие платформы, как Java JAX-RS, поддерживают использование любого разделителя, который вы хотите:

@GET
@Path("user/{id}")
User getUser(@PathParam("id") UUID id);

@GET
@Path("user/name={name}")
User getUserByName(@PathParam("name") String name);

@GET
@Path("user/email={email}")
User getUserByEmail(@PathParam("email") String email);
person Trevor Robinson    schedule 16.03.2012
comment
Это интересный подход, которого я раньше не видел. Это прекрасно решает проблему, но я просто не могу пройти мимо того факта, что, увидев знак равенства вне параметров запроса, кажется, что это вызовет путаницу... - person HDave; 06.07.2012
comment
Кроме того, это не очень успокаивает. что именно представляет собой коллекция name=john.doe? Для меня это должен быть параметр матрицы: user;name=john.doe/inbox/df87... blog.2partsmagic.com/restful-uri-design - person Maladon; 24.12.2013
comment
@Maladon Я думаю, ты упускаешь суть. name=john.doe не должно быть коллекцией, так как name — это уникальный идентификатор. Мы ищем ровно один объект или ошибку. user;name=john.doe выглядит как поиск. Я ожидаю, что он вернет то же представление, что и user, которое является коллекцией. - person Trevor Robinson; 08.06.2017
comment
Более подходящим было бы /[email protected] (или, при правильном кодировании: /user?email=john.doe%40john.doe) - person nilskp; 24.04.2019
comment
Более-более подходящим было бы /[email protected], когда вы на самом деле запрашиваете коллекцию «пользователей», используя фильтр под названием «электронная почта». Но это также означает, что результатом будет массив с «скорее всего» одним элементом. - person Sinaesthetic; 22.05.2020

Ваш первый вариант, вероятно, лучший.

Поиск пользователей по ID:

/users/id/48573

Поиск пользователей по короткому имени:

/users/name/thisisausername

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

Другой вариант, который я встречал довольно часто, — это использование параметров запроса, подобных следующему:

/users?id=48573
/users?name=thisisausername

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

person Chris Dail    schedule 08.05.2009

Довольно старый вопрос, но у меня было то же самое, и я наконец нашел решение: используйте регулярное выражение в параметре пути.

Вот как я закодировал этот вариант использования

@GET
@Path("/{id : \\d+}")
@Produces(APPLICATION_JSON)
public Response getById(@PathParam("id") long id) {
 <<your code>>
}

@GET
@Path("/{name}")
@Produces(APPLICATION_JSON)
public Response getByName(@PathParam("name") String name) {
 <<your code>>
}
person Javathought    schedule 08.06.2016
comment
Что делать, если имя пользователя числовое? Если пользователь может свободно выбирать свое имя и решает использовать только число, ваш подход терпит неудачу. - person Stefan Großmann; 02.03.2017
comment
это не удается или нет. Вы должны сделать контроль над запросом на создание (POST) и можете отказаться от только числового имени. - person Javathought; 03.03.2017
comment
@@POST public Response create(@Valid User user) и аннотируйте свою модель проверкой bean-компонента. Пример: должно начинаться с заглавной буквы, не более 50 символов @@Pattern(regexp = [A-Z][a-zA-Z_0-9-]{0,49}, message = недопустимое имя) private String name; - person Javathought; 03.03.2017
comment
Что, если мы работаем не с пользователями и именами пользователей, а с продуктами, которые имеют как (внутренний) идентификатор, так и (внешний) числовой код, например EAN? Это решение может соответствовать вопросу, но оно недостаточно общее, ИМХО. - person watery; 25.05.2021

Я бы подумал о квалификации строки с необязательным суффиксом:

/users/48573/id

/users/48573/name

Если вы получили строку без суффикса:

/users/48573

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

Если вы получаете только действительный идентификатор, но не имя, то это поиск по идентификатору, эквивалентному:

/users/48573/id

Если вы возвращаете только имя, то это поиск по имени, эквивалентный:

/users/48573/name

Если вы можете получить значение по идентификатору или имени, вы возвращаете ошибку ответа 300 и возвращаете клиенту ссылки на обе возможности:

/users/48573/id

/users/48573/name

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

person Paul Morgan    schedule 18.05.2009
comment
Это вообще не REST. Это просто РПК. - person aehlke; 20.07.2009

Ваш API не является RESTful, если это проблема. Чтобы цитировать Роя Филдинга:

REST API не должен определять фиксированные имена ресурсов или иерархии (очевидная связь клиента и сервера). Серверы должны иметь свободу управлять своим собственным пространством имен. Вместо этого разрешите серверам инструктировать клиентов о том, как создавать соответствующие URI, например, в HTML-формах и шаблонах URI, определяя эти инструкции в типах мультимедиа и связях. [Неудача здесь означает, что клиенты принимают структуру ресурсов из-за внеполосной информации, такой как стандарт для предметной области, который является ориентированным на данные эквивалентом функциональной связи RPC].

API REST следует вводить без каких-либо предварительных знаний, кроме исходного URI (закладки) и набора стандартизированных типов мультимедиа, которые подходят для целевой аудитории (т. е. ожидается, что их поймет любой клиент, который может использовать API). С этого момента все переходы между состояниями приложения должны управляться выбором клиента из предоставленных сервером вариантов, которые присутствуют в полученных представлениях или подразумеваются пользовательским манипулированием этими представлениями. Переходы могут определяться (или ограничиваться) знаниями клиента о типах мультимедиа и механизмах обмена ресурсами, оба из которых могут быть улучшены «на лету» (например, код по запросу). [Неудача здесь подразумевает, что внеполосная информация управляет взаимодействием, а не гипертекстом.]

person aehlke    schedule 20.07.2009
comment
Это не ответ на его вопрос. REST API может поддерживать HATEOAS в течение всего дня, и все же разработчикам серверов все еще нужно беспокоиться о проблемах проектирования URI. - person HDave; 06.07.2012