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

Составной ресурс REST

Я столкнулся с проблемой на работе, где я не могу найти информацию о стандартном стандарте или практике для выполнения операций CRUD в веб-службе RESTful против ресурса, чей первичный ключ является составной частью других идентификаторов ресурсов. Мы используем MVC WebApi для создания контроллеров. Например, у нас есть три таблицы:

  • Product: PK = ProductId
  • Part: PK = PartId
  • ProductPartAssoc: PK = (ProductId, PartId)

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

У нас есть классы ProductsController и PartsController, которые обрабатывают обычные операции GET/PUT/POST/DELETE с использованием шаблонов маршрутов, определенных как: {controller}/{id}/{action}, так что работают следующие IRI:

  • GET, POST /api/Products - возвращает все продукты, создает новый продукт
  • GET, PUT, DELETE /api/Products/1 - извлекает/обновляет/удаляет продукт 1
  • GET, POST /api/Parts - возвращает все части, создает новую часть
  • GET, PUT, DELETE /api/Parts/2 - извлекает/обновляет/удаляет часть 2
  • GET /api/Products/1/Parts - получить все детали для продукта 1
  • GET /api/Parts/2/Products - получить все продукты, для которых часть 2 является компонентом

В случае возникновения проблем возникает вопрос о том, как определить шаблон маршрута для ресурсов ProductPartAssoc. Каким должен быть шаблон маршрута и IRI для получения данных ассоциации? Соблюдая соглашение, я бы ожидал чего-то вроде:

  • GET, POST /api/ProductPartAssoc - возвращает все ассоциации, создает ассоциацию
  • GET, PUT, DELETE /api/ProductPartAssoc/[1,2] - извлекает/обновляет/удаляет связь между продуктом 1 и частью 2

Мои коллеги находят это эстетически недовольным, хотя и, похоже, считают, что лучше не иметь класс ProductPartAssocController, а скорее добавить дополнительные методы в ProductsController для управления данными ассоциации:

  • GET, PUT, DELETE /api/Products/1/Parts/2 - получить данные для связи между продуктом 1 и частью 2, а не данными для части 2 в качестве члена части 1, что обычно было бы основано на других примерах, таких как /Book/5/Chapter/3, что я видел в другом месте.
  • POST Не знаю, чего ожидать от IRI. К сожалению, они являются лицами, принимающими решения.

В конце дня, я думаю, что я ищу, это либо проверка, либо направление, на которое я могу указать, и сказать: "Смотрите, это то, что делают другие люди".

Какова типичная практика для работы с ресурсами, идентифицированными составными ключами?

4b9b3361

Ответ 1

Мне тоже нравится эстетика /api/Products/1/Parts/2. Вы также можете использовать несколько маршрутов для одного и того же действия, чтобы вы могли удвоиться, а также предложить /api/Parts/2/Products/1 в качестве альтернативного URL-адреса для того же ресурса.

Что касается POST, вы уже знаете составной ключ. Так почему бы не устранить необходимость в POST и просто использовать PUT для создания и обновления? POST для URL ресурса коллекции отлично, если ваша система генерирует первичный ключ, но в тех случаях, когда у вас есть составная часть уже известных первичных ключей, зачем вам нужен POST?

Тем не менее, мне также нравится идея иметь отдельный ProductPartAssocController, чтобы содержать действия для этих URL. Вам нужно будет выполнить собственное сопоставление маршрутов, но если вы используете что-то вроде AttributeRouting.NET, это очень легко сделать.

Например, мы делаем это для управления пользователями в ролях:

PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1

6 URL, но всего 3 действия, все в GrantsController (мы называем gerund между пользователями и ролями "Grant" ). Класс выглядит примерно так: AttributeRouting.NET:

[RoutePrefix("api")]
[Authorize(Roles = RoleName.RoleGrantors)]
public class GrantsController : ApiController
{
    [PUT("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
    [PUT("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
    public HttpResponseMessage PutInRole(int userId, int roleId)
    {
        ...
    }

    [DELETE("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
    [DELETE("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
    public HttpResponseMessage DeleteFromRole(int userId, int roleId)
    {
        ...
    }

    ...etc
}

Это кажется довольно интуитивным подходом ко мне. Сохранение действий в отдельном контроллере также делает более компактные контроллеры.

Ответ 2

Я предлагаю:

  • POST /api/PartsProductsAssoc: создать ссылку между частью и продуктом. Включите идентификаторы частей и продуктов в данные POST.
  • GET, PUT, DELETE /api/PartsProductsAssoc/<assoc_id>: ссылка для чтения/обновления/удаления с <assoc_id> (не является идентификатором части или продукта, да, это означает создание нового столбца в вашей таблице PartsProductsAssoc).
  • GET /api/PartsProductsAssoc/Parts/<part_id>/Products: получить список продуктов, связанных с данной частью.
  • GET /api/PartsProductsAssoc/Products/<product_id>/Parts: получить список деталей, связанных с данным продуктом.

Причины такого подхода:

  • Единый, полностью квалифицированный URI для каждой ссылки.
  • Изменение ссылки изменяет один ресурс REST.

Для получения дополнительной информации см. https://www.youtube.com/watch?v=hdSrT4yjS1g в 56:30.