Подтвердить что ты не робот

Обновление/создание иерархических ресурсов REST

Я работаю над REST API, и я пытаюсь понять, как обращаться с иерархическими ресурсами.

Фон

Начнем с простого примера. В моем API у меня есть Пользователи, Профили пользователей и Обзоры.

  • Пользователи должны иметь профиль пользователя (профиль пользователя соответствует только одному пользователю)
  • Пользователи могут иметь Обзор (обзор соответствует только одному пользователю)

Представление пользовательских ресурсов должно быть:

User: {
   "u1": "u1value", // User attributes
   "u2": "u2value",
   ...
   "links": [{
       "rel": "profile",
       "href": "http://..." // URI of the profile resource
   }, {
       "rel": "review",
       "href": "http://..." // URI of the review resource
   }]
}

Представление ресурса профиля пользователя должно быть:

UserProfile: {
   "p1": "p1value", // Profile attributes
   "p2": "p2value",
   ...
   "links": [{
       "rel": "owner",
       "href": "http://..." // URI of the user resource
   }]
}

Просмотр представления ресурсов должен быть:

Review: {
   "r1": "r1value", // Review attributes
   "r2": "r2value",
   ...
   "links": [{
       "rel": "owner",
       "href": "http://..." // URI of the user resource
   }]
}

Ресурсы URI могут быть:

  • http://api.example.com/users/{userid}: доступ к ресурсу пользователя
  • http://api.example.com/users/{userid}/profile: доступ к ресурсу пользовательского профиля
  • http://api.example.com/users/{userid}/review: доступ к ресурсу обзора пользователя

Создание ресурса: какой правильный способ создать пользователя?

Теперь я хочу создать нового пользователя:

  • POST http://api.example.com/users {"u1": "bar", "u2": "foo"} и я возвращаю новый userid = 42
  • POST http://api.example.com/users/42/profile {"p1": "baz", "p2": "asd"}
  • PUT http://api.example.com/users {"u1": "bar", "u2": "foo", links: [{"rel": "profile", "href": "http://api.example.com/users/42/profile"]}

Мои проблемы:

  • Что, если что-то перерывается между 1 и 2 или 2 и 3?
  • В 3), должен ли сервер автоматически обновлять ссылки в http://api.example.com/users/42/profile, чтобы указать на правильного владельца?
  • Обновление полей ссылок - это правильный способ создания отношений? Или я должен пропустить шаг 3), и пусть система угадывает отношения в соответствии с соглашениями URI? (Я читал в нескольких книгах, что URI следует считать непрозрачным.)
4b9b3361

Ответ 1

Ваши проблемы уместны, и ваш список вопросов верен. Если я могу предложить, ваш подход очень похож на то, что вы используете подход реляционной БД и делаете INSERT, извлекаете PK из последовательности, которую вы используете для следующего INSERT, и т.д.

Пусть сервер поддерживает ссылочную целостность

Как наблюдение, даже если после вашей исходной схемы полностью опустить шаг 3. URI в links, который отображается при извлечении вашего пользовательского документа, должен генерироваться сервером на основе существования записи профиля.

Например, если вы используете реляционный бэкэнд, вы выбираете из ПОЛЬЗОВАТЕЛЕЙ, чтобы получить запись пользователя. Затем вы выбираете из PROFILES. Если есть запись, то вы изменяете возвращаемую структуру данных, чтобы включить ссылку.

Полные документы POST

Общим способом решения других проблем, которые вы вызываете, является разрешение публикации всего документа на URL-адрес пользователя (например, базы данных NoSQL, такие как MongoDB). Здесь документ - это пользователь и профиль:

{ 
   "u1": "bar",
   "u2": "foo",
   "profile": {  
                 "p1": "baz",
                 "p2": "asd"
              }
}

В этом подходе конечная точка на сервере получает вложенную структуру (документ) и выполняет INSERT в ПОЛЬЗОВАТЕЛЯх, извлекает PK, а затем выполняет INSERT в PROFILES с использованием этой PK. Выполнение этого на стороне сервера устраняет некоторые проблемы:

  • Транзакция может быть атомной
  • Существует только один сетевой обмен между клиентом и сервером.
  • Сервер выполняет тяжелую работу и может проверять всю транзакцию (например, пользователь не создается, если профиль недействителен)
  • Нет необходимости в шаге 3.

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

GET - клиент может указать поля

Интересно сравнить с API от известных компаний. Например, LinkedIn. В своем API разработчика по умолчанию GET для пользователя возвращает просто имя пользователя, заголовок и URI.

Однако, если запрос указывает дополнительные поля, вы можете получить вложенные данные, например, второй пример в http://developer.linkedin.com/documents/understanding-field-selectors возвращает пользователя имя и список имен компаний для должностей, которые они провели. Вы можете реализовать аналогичную схему для профилей и обзоров.

PATCH для обновления свойств документа

При вставке и опротестовании, возможно, стоит рассмотреть возможность обновления (PATCH) данных. Замена поля очевидна, поэтому вы можете, например, PATCH на http://api.example.com/users/42 следующее:

{ 
   "u1": null,
   "u2": "new-foo",
   "profile": {  "p1": "new-baz"}
}

Что бы отключить u1, установите u2 на new-foo и обновите профиль p1 до new-baz. Обратите внимание, что если поле отсутствует (p2), то поле не изменяется. PATCH предпочтительнее старшего PUT, как описано в этом ответе.

Если вам нужно только обновить профиль, нажмите PATCH на новую запись профиля непосредственно на http://api.example.com/users/42/profile

DELETE должен каскадировать

Наконец, удаление может быть выполнено с помощью метода DELETE, указывающего на ресурс, который вы хотите удалить, будь то User, Profile или Review. Внедрите каскадное удаление, чтобы удалить пользователя удалил свой профиль и рецензии.

Ответ 2

Вы должны придерживаться HATEOAS и разыскивать URL-адреса, которые вы получаете по своим ответам:

Для удобства доступа, скажем, User.profile содержит href link с rel == profile.

Создайте пользователя

С помощью POST вы описали... но он не должен возвращать идентификатор, но пользователь, в комплекте с ним ссылки.

User: {
   "u1": "bar", // User attributes
   "u2": "foo",
   ...
   "profile": "http://api.example.com/users/42/profile",
   "links": [{
       "rel": "profile",
       "href": "http://api.example.com/users/42/profile"
   },
   ...
   ]
}

В этот момент ресурс профиля в файле User.profile(может быть http://api.example.com/users/42/profile или любое другое местоположение, в которое вы перенести в будущем), должно быть любым профилем по умолчанию, например. пустой документ или только с ссылкой владельца.

Обновить профиль

profile = GET User.profile
profile.p1 = "baz"
profile.p2 = "asd"
PUT profile to the same url you just dereferenced

При разыменовании hrefs на ваших документах вместо создания URL-адресов с идентификатором, который вы получаете от ответов, вам не придется менять клиент при изменении API. Как при - профили перемещаются в http://profiles.cdn.example.com/- профили получают значение p3

"Старые" клиенты API будут продолжать работать, не изменяя никакого кода.

Ответ 3

На самом деле из успешного шага (1) вы должны получить HTTP-код 201 Created и адрес (URL) для вновь созданного ресурса, а не только идентификационный номер. Если шаг (2) не удался, ваш REST API должен указать, связана ли проблема с клиентом, например с плохо сформированным документом (код выпуска 4xx) или сервером (5xx). Например, если в то же время ресурс 42 был удален, код 404 Not Found должен быть возвращен.

В этом проблема с API-интерфейсами REST без состояния: они не могут поддерживать транзакции, состоящие из нескольких запросов. Чтобы это было возможно, вам нужно будет поддерживать сеанс (состояние) на сервере.

Кстати, URL-адрес в шаге (3) в вашем примере предполагает, что вы заменяете всех пользователей и, вероятно, должны читать http://api.example.com/users/42.

У вас есть выбор между одновременным представлением полного документа пользователя + профиля, который должен быть разделен на две записи базы данных в одной атомной транзакции или чтобы обеспечить постоянство частичных пользовательских данных, то есть пользователя без профиля.

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

Ответ 4

Я считаю, что ваши вызовы должны быть такими

1) Создайте пользователя

POST http://api.example.com/users + params in payload

Если он возвращает идентификатор пользователя HTTP 201 +, вы можете продолжить и создать профиль. Кроме того, вы рассматриваете исключение так, как хотите. Вы должны дождаться, когда первый звонок вернется, прежде чем начинать создание профиля.

2) Создайте профиль, связанный с пользователем 42 (если создание пользователя было в порядке)

POST http://api.example.com/users/42/profile + params in payload

возвращает идентификатор HTTP 201 + id

Ваш сервер будет отвечать за обновление вашего объекта пользователя и объекта профиля (и вашей базы данных), чтобы пользователи 42 были связаны с новым профилем. Если бэкэнд не может связать объект, вы можете отправить обратно 500 ошибок, объясняя, что произошло.

Итак, по-моему, я пропустил бы шаг 3.

Теперь я понимаю, что вы говорите о том, что у пользователя MUST есть профиль

Я вижу 2 решения

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

2) у вас может быть свойство в вашем пользователе, говорящее, что пользователь прав или нет (что означает, что у него есть связанный профиль). В конце процесса создания, если ваш пользователь 42 не прав, вы можете удалить его или повторить создание профиля... Тогда вы могли бы запрашивать только правильных пользователей с чем-то вроде /users? IsCorrect = true

3) Позвольте клиенту учитывать тот факт, что пользователь как никакой профиль → показывает всплывающее окно, чтобы запросить создание профиля...

Посмотрите на этот документ для лучших практик API REST.

Возможно, вы могли бы также взглянуть на HAL, который пытается разобраться в отношениях между объектами.

И последнее, но не менее важное: вы можете следить за api-craft google group, вы можете найти интересные темы, связанные с вашей проблемой там.