Обработка обновления коллекции сущностей ManyToOne через REST API

Я изо всех сил думаю о том, как лучше всего справиться с обновлением коллекции до другого ресурса через REST APIs, и ищу руководство о том, как другие видят этот процесс.

предположим, что у вас есть отношения "Многие к одному" с сущностями "родитель" (один) и "ребенок" (много). Мой мыслительный процесс заключается в том, что вы можете обрабатывать обновление родительской коллекции детей через одну конечную точку PUT. Таким образом, конечная точка для обновления дочерней сущности родителя и добавления нового дочернего объекта сущности родительской коллекции выполняются через одну конечную точку. Тело запроса будет содержать массив дочерних сущностей, а сама конечная точка будет содержать достаточно информации, чтобы знать, какой родитель обновляется:

т. е. положить .../ parent / {uid} / child

конечная точка скажет нам, что родительская сущность с uid {uid} является запрашиваемой и обновляет ее дочерние сущности.

этот механизм просто чувствует себя немного ... странно. А Именно: необходимо сохранять новые сущности в одном направлении и обновлять их в другом. Мои операции обновления/сохранения предпочтительно выполняются в пакетном режиме, но странно делать как пакетное сохранение, так и обновление. Я должен сделать оба, потому что вы не можете обновить новый объект

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

Я могу выставить сообщение против Поместите различие (используя ту же конечную точку, что и выше). У меня есть ограничение, так что дочерний объект имеет уникальное имя, поэтому сообщение должно завершиться ошибкой для публикации новых дочерних объектов с существующим именем, и PUT должен завершиться ошибкой, когда тело запроса содержит дочерний объект, имя которого не существует. Вот почему я выбрал общую операцию с одной конечной точкой.

2 ответов


дело с ManyToOne

вложенные ресурсы

существует несколько способов выражения отношений, один из которых вы предложили использовать иерархию путей. Потребуются следующие конечные точки:

  • /parents/
  • /parents/:id
  • /parents/:id/children
  • /parents/:id/children/:id

этот выбор лучше выражает композиционные отношения, где дети не могут существовать самостоятельно.

ресурсы первого уровня

другой вариант, если вы применяете ограничение Hypermedia (которое вы должны назвать своим API ReSTful), является следующий:

  • /parents
  • /parents/:id
  • /childrens
  • /childrens/id

при создании дочернего ресурса необходимо включить в тело запроса ссылку на родительский ресурс с соответствующим типом rel. Например, при использовании хол:

{
  ...
  ...,
  "_links": {
    "parent": { "href": "https://api.domain.com/parents/9283jdp92cn"}
  }
}

этот выбор лучше выражает слабые отношения или агрегации, где оба конца отношений могут существовать без одного другой.

контекст безопасности

есть третий вариант, который мы должны рассматривать как особый случай. Если родительский ресурс является аутентифицированным субъектом, вы можете неявно связать его с другим. Например, если родителем является User объект домена является владельцем коллекции Photos, у вас может возникнуть соблазн разоблачить следующее конечные точки:

  • /users
  • /users/:id
  • /users/:id/photos
  • /users/:id/photos/:id

учитывая, что только User может получить доступ только к своим Photos, этого было бы достаточно:

  • /photos
  • /photos/:id

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

дополнительные соображения

исходя из вашего вопроса, я нахожу некоторые сигналы того, что может привести к внедрению плохих практик. Так вот некоторые принципы, касающиеся вашего поста, что вы должны стараться придерживаться, когда это возможно (прагматическом).

  1. каждому ресурсу нужен уникальный идентификатор, URI в ReST. Как вы уже обнаружили, не так будет странные последствия при попытке обновить дочерние объекты, как указано в вашем вопросе.
  2. ваш API, чтобы быть ReSTful, должен реализовать ограничение гипермедиа. Если идентификаторами ресурсов являются URIs, можно создать полные и расширенные отношения между ресурсами независимо от их расположения на сервере.
  3. идентификаторы должны быть непрозрачными. Поэтому никогда не используйте коррелятивные числа или перечисления в :id часть URI. Даже не позволяйте своим потребителям пытаться угадать URIs это может показать то, что вы не хотите, чтобы они видели. Безопасность является ключевым, но непрозрачные id являются дополнительными.
  4. если по уважительной причине идентификаторы не должны генерироваться сервером, а не клиентом. Иначе ваши документы не были бы непрозрачными.
  5. как правило:

    1. создать с POST и возврат 201 Created. Мне нравится отвечать телом ресурса. И не забудьте включить URI в созданный ресурс.
    2. читать с GETи возврат 200 OK.
    3. изменить весь ресурс с PUT. Мне нравится быть последовательным с post и возвращать обновленный ресурс с 200 OK.
    4. удалить с DELETE и с 204 No Content.
    5. я редко использую PATCH для частичного обновления.

это позволяет вам охватить большинство случаев.


есть ли лучший способ сделать это?

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

GET /people/bob/favoriteColors

200 OK
[]

если это начальная точка, и мы хотим добавить новый цвет в список

PUT /people/bob/favoriteColors
[ "RED" : { "redChannel":255, "greenChannel":0, "blueChannel":0} ]

200 OK

нет проблем, мы "создали" любимый цвет с КЛАСТЬ.

PUT /people/bob/favoriteColors
[ "RED" : { "redChannel":239, "greenChannel":0, "blueChannel":0} ,
[ "BLUE" : { "redChannel":16, "greenChannel":16, "blueChannel":239} ]

200 OK

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

KeyValueStore.put(/people/bob/favoriteColors, [...])

KeyValueStore.put(/people/bob/favoriteColors/RED, {...})
KeyValueStore.put(/people/bob/favoriteColors/BLUE, {...})

KeyValueStore.put(/people/bob, {...,favoriteColors:{...}})

RDBMS.commit( [ favoriteColors.insert(BLUE : {}), favoriteColors.update(RED: {})

что не означает, что ваш api не должен разрешать публикацию непосредственно на новый ресурс; это тоже хорошо

PUT /people/bob/favoriteColors/OCTARINE
{ "redChannel":-inf, "greenChannel":Nan, "blueChannel":i}

201 CREATED

что вам нужно иметь в виду, так это то, что с точки зрения вашего болотного стандарта, из коробки, промежуточные компоненты, есть нет подразумеваемых отношений между /people/bob/favoriteColors и /people/bob/favoriteColors/OCTARINE. Изменение одного не делает недействительными записи кэша для другого-тот же интерфейс, который защищает нас от деталей реализации записей, также защищает нас от побочных эффектов на других ресурсах. При разработке API вам нужно продумать последствия наличия нескольких ресурсов, которые могут изменить "одно и то же" состояние.

в какой-то степени у вас, вероятно, есть эта проблема в любом случае. Посредников не буду знать, что

DELETE /people/bob

должны и выселить /people/bob/favoriteColors

во всех примерах до этого момента я использовал полные представления адресованного ресурса. Вот и все, что PUT разрешено делать-отправлять представление замены для целевого ресурса. Если вы хотите отправить представление изменения, вам нужно подумать о патче или сообщении.

соединение POST для создания является ложным. Я думаю, что соединение предполагалось в ответ на описание поста в RFC 2616. Язык в [RFC 7231] трактует это как нечто более всеобъемлющее. POST-единственный метод записи, поддерживаемый в HTML, и веб процветал, поэтому мы должны как-то управлять.

дополнительный выход-отправлять сообщения, где вся интересная работа-побочный эффект. Это аналогично отправке сообщения в очередь сообщений. Целевой ресурс-сама очередь, поэтому ваши запросы и все ответы соответствуют концептуальной модели добавления документа в коллекцию; поскольку сами документы являются представлениями выполняемой работы, а не представлениями результатов, они стоят отдельно от самой модели домена. Это означает, что вы можете отправить полное представление BeigifyColors команда, которая может быть произвольно сложной, для обработчиков, настроенных на этот конкретный случай использования, и наблюдать побочные эффекты в ваших представлениях.

работа ресурсы здесь, читаемые представления существует другое выражение шаблона CQRS. Как и в случае с PUT OCTARINE, посредники не будут знать, какие представления выселять, но в противном случае они обрабатывают протокол просто отлично.