Обработка обновления коллекции сущностей 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
объект домена является владельцем коллекции Photo
s, у вас может возникнуть соблазн разоблачить следующее конечные точки:
/users
/users/:id
/users/:id/photos
/users/:id/photos/:id
учитывая, что только User
может получить доступ только к своим Photo
s, этого было бы достаточно:
/photos
/photos/:id
потому что аутентифицированный User
будет доступен для конечной точки через контекст безопасности, и отношения могут быть неявно сделаны без явно выражая его через иерархический путь или другими средствами.
дополнительные соображения
исходя из вашего вопроса, я нахожу некоторые сигналы того, что может привести к внедрению плохих практик. Так вот некоторые принципы, касающиеся вашего поста, что вы должны стараться придерживаться, когда это возможно (прагматическом).
- каждому ресурсу нужен уникальный идентификатор, URI в ReST. Как вы уже обнаружили, не так будет странные последствия при попытке обновить дочерние объекты, как указано в вашем вопросе.
- ваш API, чтобы быть ReSTful, должен реализовать ограничение гипермедиа. Если идентификаторами ресурсов являются URIs, можно создать полные и расширенные отношения между ресурсами независимо от их расположения на сервере.
- идентификаторы должны быть непрозрачными. Поэтому никогда не используйте коррелятивные числа или перечисления в
:id
часть URI. Даже не позволяйте своим потребителям пытаться угадать URIs это может показать то, что вы не хотите, чтобы они видели. Безопасность является ключевым, но непрозрачные id являются дополнительными. - если по уважительной причине идентификаторы не должны генерироваться сервером, а не клиентом. Иначе ваши документы не были бы непрозрачными.
-
как правило:
- создать с
POST
и возврат201 Created
. Мне нравится отвечать телом ресурса. И не забудьте включить URI в созданный ресурс. - читать с
GET
и возврат200 OK
. - изменить весь ресурс с
PUT
. Мне нравится быть последовательным с post и возвращать обновленный ресурс с200 OK
. - удалить с
DELETE
и с204 No Content
. - я редко использую
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
команда, которая может быть произвольно сложной, для обработчиков, настроенных на этот конкретный случай использования, и наблюдать побочные эффекты в ваших представлениях.