RESTful атомное обновление нескольких ресурсов?
Представьте, что веб-приложение хранит некоторый ресурс данных с некоторым идентификатором, который хранит три вложения (например, pdf) для каждого элемента данных.
Схема URL
data/{id}/attachment1
data/{id}/attachment2
data/{id}/attachment3
Существует API RESTful для вложений, предоставляющих операции GET/PUT/DELETE, реализующие операции CRUD на стороне сервера.
Если id равен 123, я хотел бы выполнить операцию, в которой
- attachment1 заменяется новым вложением (таким образом, что
GET
file/123/attachment1
возвращает новое вложение)
- attachment2 удаляется (так что
GET file/123/attachment2
возвращает 404)
- attachment3 остается неизменным.
Обновление должно быть атомарным - полное обновление выполняется сервером или вообще ничего.
Применение простых PUT file/123/attachment1
и DELETE file/123/attachment2
не является атомарным, так как клиент может потерпеть крах после PUT, и на сервере нет намека на то, что он должен сделать откат в этом случае.
Итак, как мне выполнить операцию с помощью RESTful?
Я думал о двух решениях, но оба они не кажутся 100% RESTful:
- Используйте PATCH (может быть PUT, но PATCH лучше отражает семантику
частичное обновление) с multipart/form-data по данным /123:
multipart/form-data - это последовательность объектов, состоящих из нового
"application/pdf", связанное с полем "attachment1" и
то, что будет представлять нулевое значение для обозначения удаления
attachment2.
Хотя это обеспечивает атомарность, я сомневаюсь, что это RESTful, поскольку я перегружаю метод PATCH, используя разные списки параметров, что нарушает ограничение унифицированного интерфейса.
- Используйте ресурс, представляющий транзакцию. Я мог бы отправить данные ID 123
URL-адрес транзакции, который будет создавать ресурс транзакции
представляющий копию текущего состояния хранимого ресурса данных
на сервере, например. транзакции/данные/123. Теперь я могу вызвать PUT и
УДАЛИТЬ на вложения этого временного ресурса (например,
DELETE transaction/data/123/attachment2
) и сообщить
фиксация этой версии ресурса на сервере через PUT on
транзакции/данные/123. Это обеспечивает атомарность, когда приходится
реализовать дополнительную логику на стороне сервера для работы с несколькими клиентами
изменение того же ресурса и разбитых клиентов, которые никогда не совершались.
Хотя это, похоже, согласуется с REST, похоже, это нарушает противоречие безгражданства. Состояние транзакционного ресурса - это не состояние службы, а состояние приложения, поскольку каждый транзакционный ресурс связан с одним клиентом.
Я как бы застрял здесь, поэтому любые идеи были бы полезны, спасибо!
Ответы
Ответ 1
Вы хотите использовать второй вариант, вариант транзакции.
Что вам не хватает, так это создание транзакции:
POST /transaction
HTTP/1.1 301 Moved Permanently
Location: /transaction/1234
Теперь у вас есть ресурс транзакции, который является гражданином первого класса. Вы можете добавить к нему, удалить из него, запросить его текущее содержимое, а затем, наконец, выполнить его или удалить (т.е. Откат) транзакцию.
Пока транзакция выполняется, это еще один ресурс. Здесь нет состояния клиента. Любой пользователь может добавить к этой транзакции.
Когда все это сделано, сервер применяет изменения все сразу, используя какой-то внутренний механизм транзакций, который находится вне области действия.
Вы можете записывать такие вещи, как Etags и if-modified заголовки в подзаголовках транзакций, чтобы, когда они все применяются, вы знаете, что что-то не изменилось за вашей спиной.
Ответ 2
Очень интересный вопрос. Профессор C.S. в университете Лугано (Швейцария) написал несколько слайдов об этой ситуации:
http://www.slideshare.net/cesare.pautasso/atomic-transactions-for-the-rest-of-us
Однако я не уверен, что предоставляемое им решение полностью RESTful, потому что на серверной стороне оно не похоже на апатию.
Будучи честным, поскольку сама транзакция состоит из нескольких состояний, я не думаю, что для этой проблемы может быть полностью RESTful решение.
Ответ 3
Предполагая, что ваши URI являются иерархическими:
PUT data/{id}
[attachment2,attachment3]
Часть вашей проблемы заключается в том, что attachment1/2/3 является ужасным идентификатором. Индекс никогда не должен быть частью ваших URI.
Ответ 4
Я не испытываю, но у меня есть идея для решения, поскольку я сталкиваюсь именно с этой проблемой в разработке.
Во-первых, я использую аналогию со мной (клиентом), отправляя сообщение в Fred1 в доме (сервер с ресурсами), что я хочу, чтобы он выключил переключатель освещения (изменение состояния части ресурса) и включил чайник (изменение состояния другой части ресурса). После отключения выключателя света Фред, к сожалению, имеет сердечный приступ.
Теперь у меня нет ничего от Фреда, чтобы сказать, сделал ли он то, что я спросил. Фреда заменяет другой Фред. Сообщение, которое я отправил, не получил ответа. Единственный способ, которым я могу продолжить, - спросить Fred2, если выключатель света выключен, и чайник включен (ресурс находится в состоянии, которое я ожидал бы после того, как я попросил его сделать что-то для меня). Это неудачное состояние дел (ошибка) и добавляет к моей нагрузке, но теперь я могу исходить из того, что я знаю, что сделал Фред1 перед его сердечным приступом. Я могу либо вернуться к чертежной доске (информировать пользователя о том, что что-то пошло не так, и нам нужно его переделать), либо внести изменения, которые завершили бы мой запрос, если это все еще актуально (включите чайник).
Это начало того, как я это сделаю, очевидно, что существует проблема с re-областью, но если я уже определил свою область (меня интересует только переключатель света и чайник), тогда у меня должно быть достаточно информации ( зная состояние переключателя света и чайник), чтобы дать новую команду Fred2, не возвращаясь к пользователю для инструкций.
Как это звучит?