Служба REST ресурса составного ключа

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

  • Product: ПК=идентификатор продукта
  • Part: ПК=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 будет выглядеть. К сожалению, они принимают решения.

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

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


person Mitselplik    schedule 23.04.2013    source источник
comment
Для связывания сущностей вы можете смоделировать пространство uri, как протокол OData указывает для служб Odata. я не предлагаю внедрять службу OData, но на нее стоит взглянуть, поскольку она дает полезную информацию и ближе к вашей проблеме. См. здесь информацию об управлении связями между объектами: odata.org/documentation/odata. -v2-документация/операции/   -  person Kiran Challa    schedule 24.04.2013
comment
Как выглядит ProductPartAssoc? И какие операции CRUD он поддерживает?   -  person RaghuRam Nadiminti    schedule 24.04.2013
comment
Пример был произвольным, но класс ProductPartAssoc мог бы выглядеть примерно так (извините за краткость — здесь мало места): class ProductPartAssoc { int ProductId; внутренний идентификатор части; целая суммаИспользовано; десятичная сборкаСтоимость; внутренний идентификатор установщика; } и должна поддерживать все четыре операции CRUD — создание, извлечение, обновление, удаление.   -  person Mitselplik    schedule 24.04.2013


Ответы (2)


Мне тоже нравится эстетика /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 (мы называем герундий между пользователями и ролями «Грант»). Класс в конечном итоге выглядит примерно так, используя 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
}

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

person danludwig    schedule 24.04.2013
comment
Спасибо за понимание :-) Две вещи: (1) Спасибо за совет по AttributeRouting.Net - отличный материал. (2) Оглядываясь назад, вы правы в том, что вам не нужна операция POST. Спасибо что подметил это. Я отметил ваш ответ как ответ на мой вопрос... - person Mitselplik; 26.04.2013

Я предлагаю:

  • 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.

person elplatt    schedule 19.02.2015