HTTP ReST: обновлять большие коллекции: лучший подход, чем JSON PATCH?

Я разрабатываю веб-службу для регулярного получения обновлений списков. На этом этапе список все еще можно смоделировать как единую сущность (/lists/myList) или фактическую коллекцию с множеством ресурсов (/lists/myList/entries/<ID>). Списки большие (миллионы записей), а обновления небольшие (часто менее 10 изменений).

Клиент получит URL-адреса и списки веб-служб для распространения, например:

Затем он будет отправлять списки и обновления в соответствии с настройками. Вероятно, но неизвестно, существует ли какая-то база данных за URL-адресами веб-службы.

Я исследовал, и кажется, что HTTP PATCH с использованием формата патча JSON является лучшим подходом.

Контекст и примеры: у каждого списка есть идентифицирующее имя, приоритет и миллионы записей. Каждая запись имеет идентификатор (определяемый клиентом) и несколько необязательных атрибутов. Пример создания списка «requiredItems» с приоритетом 1 и двумя элементами списка:

PUT /lists/requiredItems
Content-Type: application/json


{
  "priority": 1,
  "entries": {
    "1": {
      "color": "red",
      "validUntil": "2016-06-29T08:45:00Z"
    },
    "2": {
      "country": "US"
    }
  }
}

Для обновлений клиенту сначала нужно знать, как сейчас выглядит список на сервере. Для этого я бы добавил свойство «ревизия» в сущность списка.

Затем я бы запросил этот атрибут:

GET /lists/requiredItems?property=revision

Затем клиент увидит, что нужно изменить между версией на сервере и последней версией, известной клиенту, и создаст патч JSON. Пример:

PATCH /list/requiredItems
Content-Type: application/json-patch+json

[
  { "op": "test", "path": "revision", "value": 3 },
  { "op": "add", "path": "entries/3", "value": { "color": "blue" } },
  { "op": "remove", "path": "entries/1" },
  { "op": "remove", "path": "entries/2/country" },
  { "op": "add", "path": "entries/2/color", "value": "green" },
  { "op": "replace", "path": "revision", "value": 10 }
]

Вопросы:

  • У этого подхода есть недостаток, заключающийся в несколько меньшей поддержке клиентов из-за нечасто используемой HTTP-команды PATCH. Существует ли более совместимый подход без ущерба для совместимости с HTTP (идемпотентность и так далее)?
  • Моделирование отдельных записей списка как отдельных ресурсов и использование PUT и DELETE (возможно, с ETag и/или If-Match) кажется вариантом (PUT /lists/requiredItems/entries/3, DELETE /lists/requiredItems/entries/1 PUT /lists/requiredItems/revision), но как я могу убедиться, что все эти операции применяются, когда сеть падает посередине? цепочки обновлений? Разрешено ли HTTP PATCH работать с несколькими ресурсами?
  • Есть ли лучший способ «версировать» списки, возможно, неявно улучшая способ их обновления? Обратите внимание, что клиент определяет номер версии.
  • Правильно ли запрашивать номер версии с помощью GET /lists/requiredItems?property=revision? Должен ли это быть отдельный ресурс, например /lists/requiredItems/revision? Если это должен быть отдельный ресурс, как бы я обновил его атомарно (т. е. список и ревизия обновляются или оба не обновляются)?
  • Будет ли работать в патче JSON, чтобы сначала проверить значение ревизии на 3, а затем обновить его до 10 в том же патче?

person Tomas Creemers    schedule 29.06.2016    source источник
comment
Пожалуйста, не задавайте более одного вопроса одновременно. Все они действительны, но вы, вероятно, не получите ответ на все из них.   -  person    schedule 29.06.2016
comment
@LutzHorn: спасибо. Ты прав. Должен ли я удалить все вопросы, кроме одного, и опубликовать остальные отдельно? Вступительная часть будет такой же...   -  person Tomas Creemers    schedule 29.06.2016


Ответы (2)


У этого подхода есть недостаток, заключающийся в несколько меньшей поддержке клиентов из-за нечасто используемой HTTP-команды PATCH.

Насколько я могу судить, PATCH действительно подходит только в том случае, если ваш сервер действует как тупое хранилище документов, где действие буквально «пожалуйста, обновите свою копию документа в соответствии со следующим описанием».

Так что, если ваш ресурс действительно представляет собой документ JSON, описывающий список с миллионами записей, тогда JSON-Patch — отличный ответ.

Но если вы ожидаете, что патч в качестве побочного эффекта обновит объект в вашем домене, то я подозреваю.

Разрешено ли HTTP PATCH работать с несколькими ресурсами?

RFC 5789

Метод PATCH влияет на ресурс, указанный в Request-URI, а также МОЖЕТ иметь побочные эффекты на другие ресурсы.

Я не заинтересован в запросе номера версии; похоже, у него нет явного преимущества перед использованием подхода ETag/If-Match. Некоторые очевидные недостатки - кэши между вами и клиентом не знают, что список и номер версии связаны; кеш с радостью сообщит клиенту, что версия 12 списка — это версия 7, или наоборот.

person VoiceOfUnreason    schedule 29.06.2016
comment
Спасибо! Веб-служба может иметь или не иметь за собой базу данных; Я разрабатываю клиент и веб-службу, но не серверную часть. На стороне сервера будет несколько различных сторонних реализаций. Я добавил некоторые пояснения в начале вопроса. - person Tomas Creemers; 29.06.2016
comment
Я не понимаю, как HTTP надеется включить кэширование, если PATCH для /service/list1 может влиять на любой другой ресурс. Просто аннулировать весь кеш каждый раз, когда запрашивается PATCH? - person Tomas Creemers; 29.06.2016
comment
Справедливости ради PATCH, те же проблемы возникают с POST, PUT, DELETE. Когерентность кэша — одна из двух сложных проблем. - person VoiceOfUnreason; 29.06.2016
comment
JSON-Patch — худшая идея. Удивительно, как она проникла в столько сердец и умов. - person fiatjaf; 08.04.2017

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

ETags — это «хэши» ресурсов HTTP. Их можно комбинировать с заголовками If-Match для создания системы управления версиями. Однако заголовки ETag обычно не используются для объявления ETag ресурса, который создается (PUT) или обновляется (POST/PATCH). Сервер, на котором хранится ресурс, обычно определяет ETag. Я не нашел ничего, что явно запрещало бы это, но многие реализации могут предположить, что сервер определяет ETag, и запутаться, когда он предоставляется с помощью PUT или PATCH.

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

На уровне HTTP семантически невозможно иметь транзакции фиксации/отката, если только не смоделировать саму транзакцию как ресурс ReST, что значительно усложнило бы ситуацию.

Однако некоторые свойства PATCH позволяют использовать его для этого:

  • A HTTP PATCH must be atomic and can operate on multiple resources. RFC 5789:
    • The server MUST apply the entire set of changes atomically and never provide (e.g., in response to a GET during this operation) a partially modified representation. If the entire patch document cannot be successfully applied, then the server MUST NOT apply any of the changes.
    • Метод PATCH влияет на ресурс, указанный в Request-URI, а также МОЖЕТ иметь побочные эффекты на другие ресурсы; т. е. новые ресурсы могут быть созданы или существующие изменены путем применения PATCH. PATCH не является ни безопасным, ни идемпотентным
  • JSON PATCH может состоять из нескольких операций с несколькими ресурсами, и все они должны применяться или не должны применяться ни один из них, что делает его неявной транзакцией. RFC 6902:
    Операции применяются последовательно в том порядке, в котором они появляются в массиве.

Таким образом, ревизия может быть смоделирована как отдельный ресурс и при этом обновляться. Запрос текущей версии — это простой GET. Фиксация транзакции — это одиночный запрос PATCH, содержащий сначала проверку ревизии, затем операции над ресурсом (ресурсами) и, наконец, операцию по обновлению ресурса ревизии.

Сервер по-прежнему может опубликовать версию как ETag основного ресурса.

person Tomas Creemers    schedule 17.07.2016