Дизайн REST API для операции сброса до значений по умолчанию

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

Предположим, у меня есть одноэлементный ресурс с именем Settings. Он создается при инициализации/установке моего веб-сервера, но некоторые пользователи могут изменять его через REST API, скажем, /settings — это мой URI. У меня есть операция GET для получения настроек (в формате JSON) и операция PATCH для установки одного или нескольких значений.

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

  1. Используйте операцию DELETE для ресурса. В конце концов, это идемпотент, и это довольно ясно (для меня). Но поскольку URI все еще будет существовать после DELETE, то есть ресурс не был ни удален, ни перемещен в недоступное место, это противоречит определению RESTful для DELETE.
  2. Используйте POST для выделенной конечной точки, такой как /settings/reset - мне очень не нравится этот, потому что он наиболее явно не RESTful, поскольку глагол находится в URI
  3. Используйте ту же операцию PATCH, передав некоторую замену для значения по умолчанию, например значение null. Проблема, с которой я сталкиваюсь, заключается в том, что результат операции отличается от ввода (я устанавливаю для свойства значение null, затем получаю его, и оно имеет строковое значение)
  4. Создайте отдельную конечную точку для GET значений по умолчанию, таких как /setings/defaults, а затем используйте ответ в PATCH для установки этих значений. Кажется, это никоим образом не противоречит REST, но требует 2 вызовов API для, казалось бы, одной простой операции.

Если один из приведенных выше методов считается лучшим, или если есть один, который я не перечислил выше, я хотел бы услышать об этом.

Изменить:

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

В моем случае я разрабатываю API для существующего продукта. Он имеет веб-интерфейс для обычного пользователя, а также REST API, предназначенный для удовлетворения потребностей разработчиков, которым необходимо автоматизировать определенные задачи с помощью указанного продукта. В этом упрощенном примере я могу развернуть продукт в тестовой среде, в которой я запускаю различные автоматические тесты, изменяющие /settings, и хотел бы запустить сценарий очистки, который сбрасывает /settings в нормальное состояние, когда я закончу.

Продукт не является SaaS (пока), а API-интерфейсы не являются общедоступными (например, любой в Интернете может получить к ним свободный доступ) — поэтому аудитория и, следовательно, потенциальные типы клиентов, с которыми я могу столкнуться, довольно малы — разработчики, которые используют мой продукт, развернутый в их частном центре обработки данных или на компьютерах AWS EC2, и им необходимо написать сценарий на любом языке для автоматизации какой-либо задачи, а не выполнять ее через пользовательский интерфейс.

Это означает, что некоторые технические соображения, такие как кэширование, имеют значение. Соображения человека-пользователя, такие как согласованность дизайна API для различных ресурсов и простота его изучения, также имеют значение. Но может ли какой-нибудь сторонний поисковый робот определить следующие действия, которые он может выполнить из заданного состояния, не так важно (именно поэтому мы вообще не реализуем HATEOAS или метод OPTIONS)


person motig88    schedule 18.02.2021    source источник
comment
Я также не знаю ни о какой «лучшей практике», так что это будет только мое личное мнение — 1. Согласен с вашим анализом — это не похоже на интуитивное поведение DELETE для меня 2. Согласен, что это не REST по книге. Однако я сразу могу понять, что он делает 3. Согласен еще раз; кроме того, это не только неинтуитивно, что, если null является допустимым значением для одного из параметров? так что если я отправлю option: null, что я имею в виду? установить его на null или восстановить по умолчанию? 4. Кажется обременительным В конце концов, REST — это не религия; Я бы выбрал то, что легче всего понять; ИМО вариант 2   -  person J. Ed    schedule 18.02.2021
comment
некоторые пользователи могут изменить его через REST API - почему один пользователь хочет сбросить его, а другой - нет? Будет ли это иметь последствия для других пользователей, которые не сбросили настройки?   -  person codebrane    schedule 18.02.2021
comment
@codebrane позволяет сказать, что в случае / settings это может делать только пользователь с ролью / уровнем разрешений системного администратора. На самом деле не имеет отношения к сути этого вопроса, я не должен был сформулировать это таким образом.   -  person motig88    schedule 18.02.2021


Ответы (2)


Давайте сначала обсудим упомянутые вами варианты:

По сути, в архитектуре REST сервер предлагает клиенту множество вариантов, который в зависимости от его задачи выбирает один из вариантов и выдает запрос на прикрепленный URI. Обычно сервер обучает клиента всему, что ему нужно знать, через представления форм, такие как Формы HTML, формы HAL или ION.

В такой среде settings, как вы упомянули, является действительным ресурсом сам по себе, а также ресурсом настроек по умолчанию. Таким образом, чтобы позволить клиенту сбросить свои настройки, достаточно просто скопировать содержимое ресурса настроек по умолчанию в целевой ресурс настроек. Если вы хотите быть совместимым с WebDAV, который является просто расширением HTTP, вы можете использовать Операция COPY HTTP (также см. другие зарегистрированные операции HTTP в IANA) ). Однако для простых HTTP-клиентов вам может понадобиться другой подход, чтобы любые произвольные HTTP-клиенты могли сбросить настройки до желаемого значения по умолчанию.

Как сервер хочет, чтобы клиент выполнял этот запрос, можно узнать с помощью вышеупомянутой поддержки форм. Очень упрощенный подход в Интернете состоял бы в том, чтобы отправить клиенту HTML-страницу с настройками, предварительно заполненными в HTML-форме, возможно, также позволить пользователю заранее настроить свои настройки в соответствии с его пожеланиями, а затем нажать кнопку отправки, чтобы отправить запрос к URI, представленному в атрибуте действия формы, который может быть любым URI, который хочет сервер. Поскольку HTML поддерживает только POST и GET в формах, в Интернете вы ограничены POST.

Можно подумать, что достаточно просто отправить полезную нагрузку, содержащую URI ресурса настроек для сброса и, возможно, URI настроек по умолчанию, на выделенную конечную точку через POST, а затем позволить ему выполнить свою магию, чтобы сбросить состояние до значения по умолчанию. Однако этот подход обходит кеши и может заставить их поверить, что старое состояние все еще действительно. Кэширование в HTTP работает таким образом, что де-факто URI ресурса используется в качестве ключа, и любые небезопасные операции, выполняемые с этим URI, приведут к вытеснению этого сохраненного контента, так что вместо этого любые последовательные запросы будут напрямую направляться на сервер. вместо этого обслуживается кешем. Когда вы отправляете небезопасный POSTrequest на выделенный ресурс (или конечную точку с точки зрения RPC), вы упускаете возможность сообщить кэшу об изменении фактического ресурса настроек.

Поскольку REST — это просто обобщение модели взаимодействия, используемой в человеческой сети, неудивительно, что те же самые концепции, используемые в Интернете, применимы и к уровню домена приложения. Хотя здесь также можно использовать HTML, форматы на основе JSON, такие как application /hal+json или вышеупомянутые форматы HAL forms или ION, вероятно, более популярны. В целом, чем больше типов мультимедиа может поддерживать ваш сервис, тем больше вероятность того, что сервер будет обслуживать множество клиентов.

В отличие от человеческого Интернета, где изображения, кнопки и прочее предоставляют доступность соответствующего контроля пользователю, произвольные клиенты, особенно автоматизированные, обычно плохо справляются с такими возможностями. Таким образом, должны быть предоставлены другие способы намекнуть клиенту о назначении URI или элемента управления, такие как имена отношений ссылок. В то время как <<, <, >, >> могут использоваться в ссылке HTML-страницы для обозначения первого, предыдущего, следующего и последнего элементов в коллекции, отношение ссылки здесь предоставляет first, prev, next и last в качестве альтернативы. Такие отношения ссылок должны быть либо зарегистрированы в IANA, либо по крайней мере следуйте подходу расширения веб-ссылок. Клиент, ищущий URI в отношении prev, будет знать цель URI, а также сможет взаимодействовать с сервером, если URI когда-либо изменится. По сути, это также то, что представляет собой HATEOAS, используя заданные элементы управления для навигации по приложению через конечный автомат, предлагаемый сервером.

Вот некоторые общие практические правила разработки приложений для архитектур REST:

  • Спроектируйте взаимодействие так, как если бы вы взаимодействовали с веб-страницей в Интернете, или, более формально, как с конечным автоматом или протокол приложения домена, как назвал его Джим Уэббер, клиент может работать через
  • Позвольте серверам обучать клиентов тому, как должны выглядеть запросы, благодаря поддержке различных типов форм.
  • API не должны использовать типизированные ресурсы, но вместо этого полагайтесь на согласование типа контента
  • Чем больше типов мультимедиа поддерживает ваш API или клиент, тем выше вероятность взаимодействия с другими узлами.

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


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

REST в смысле архитектурного стиля Филдинга сияет, когда существует множество различных клиентов, взаимодействующих с вашим приложением, и когда необходима поддержка будущей эволюции, неотъемлемо интегрированная в дизайн. REST просто дает вам возможность добавлять новые функции в будущем, а клиенты REST с хорошим поведением просто подберут их и продолжат работу. Если вы взаимодействуете только с очень ограниченным набором клиентов, особенно под вашим контролем, или если вероятность будущих изменений очень мала, REST может быть излишним и не оправдывать дополнительные накладные расходы, вызванные тщательным проектированием и реализацией.

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

Термин API design уже указывает на то, что желателен подход, более похожий на RPC, когда определенные операции доступны пользователю, которые могут вызываться для выполнения некоторых задач. Это все нормально, если вы не называете это REST API из Точка зрения Филдинга. Простая истина здесь заключается в том, что практически нет приложений/систем, которые действительно следуют архитектурному стилю REST, но есть множество плохих примеров, которые неправильно используют термин REST и, следовательно, дают неправильное представление об архитектуре REST, ее цели, а также его преимущества и недостатки. В некоторой степени это проблема, вызванная тем, что люди не читают диссертацию Филдинга (внимательно), а частично из-за общего предпочтения прагматизма и использования / реализации ярлыков для выполнения работы как можно скорее.

Что касается прагматичного взгляда на REST, трудно дать точный ответ, поскольку все, похоже, понимают в нем разные вещи. Большинство этих API в любом случае полагаются на внешнюю документацию, такую ​​​​как Swagger, OpenAPI и тому подобное, и здесь URI, похоже, является тем, что дает разработчикам представление о цели. Таким образом, URI, оканчивающийся на .../settings/reset, должен быть понятен большинству разработчиков. Независимо от того, имеет ли URI запах RPC или следует ли следовать семантике соответствующих HTTP-операций, то есть частичных PUT или полезных данных внутри GET, это ваш выбор дизайна, который вы должны задокументировать.

person Roman Vottner    schedule 18.02.2021
comment
Это фантастический ответ. Я очень ценю время и мысль, что вы потратили на его написание, и это определенно помогло мне исключить (или оправдать исключение) некоторые из перечисленных мною подходов. Я обновил свой исходный пост некоторыми дополнительными подробностями о моем случае использования - использование форм в моем случае не имеет значения, но логически я вижу, как это приведет к тому, что я пытаюсь сделать. Спасибо! - person motig88; 18.02.2021

Можно использовать POST

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

POST /settings HTTP/x.y
Content-Type: text/plain

Please restore the default settings

В Интернете вы, скорее всего, увидите это в результате отправки формы; эта форма может быть встроена в представление ресурса /settings или находиться в отдельном документе (это будет зависеть от таких соображений, как кэширование). В этом параметре полезная нагрузка запроса может измениться:

POST /settings HTTP/x.y
Content-Type: application/x-www-form-urlencoded

action=restoreDefaults

С другой стороны: если бы семантику этого сообщения стоило стандартизировать (т. е. если бы многие ресурсы в Интернете могли одинаково понимать значения восстановления по умолчанию), то вместо этого вы бы зарегистрируйте определение нового токена метода, протолкните его через процесс стандартизации и содействуйте принятию.

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


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

Здесь есть напряжение, на которое следует обратить внимание:

  • Эталонное приложение для архитектурного стиля REST — это всемирная паутина.
  • Единственным небезопасным методом, поддерживаемым HTML-формами, был POST.
  • Сеть имела катастрофический успех

Одна из идей, лежащих в основе этого, заключается в том, что интерфейс был унифицированным — браузеру не нужно знать, относится ли какой-либо идентификатор к ресурсу коллекции, ресурсу члена, документу, изображению или чему-то еще. Как и промежуточные компоненты, такие как кэши и обратные прокси. Все разделяют одинаковое понимание описательных сообщений... даже намеренно расплывчатых, таких как POST.

Если вам нужно сообщение с более конкретной семантикой, чем POST, вы регистрируете для него определение. Это, например, именно то, что произошло в случае с PATCH — кто-то сделал это что определение нового метода с дополнительными ограничениями на семантику полезной нагрузки позволило бы использовать более богатые и мощные компоненты общего назначения.

То же самое могло бы произойти с семантикой CREATE, если бы кто-то был достаточно умен, чтобы сесть и обосновать ситуацию (опять же: как компоненты общего назначения могут воспользоваться дополнительными ограничениями на семантику?)

Но до тех пор эти сообщения должны использовать POST, а компоненты общего назначения не должны предполагать, что POST имеет семантику создания, потому что RFC 7231 не содержит этих дополнительных ограничений.

person VoiceOfUnreason    schedule 18.02.2021
comment
Спасибо за эту ссылку! Однако в нем есть кое-что, что противоречит этой идее использования POST для сброса. Единственное, что REST требует от методов, — это чтобы они были единообразно определены для всех ресурсов. Если большинство моих ресурсов — это типичные CRUD-коллекции, где общепринято, что POST создаст новый ресурс заданного типа, но для ресурса /settings он делает что-то совершенно другое (сбрасывает значения по умолчанию). - person motig88; 18.02.2021
comment
@motig88 POST — это универсальный метод, который необходимо использовать в случае любого другого HTTP-операции семантика не соответствует цели запроса. Кроме того, создание нового ресурса должно быть указано с заголовком Location в ответе на запрос POST или PUT. - person Roman Vottner; 18.02.2021
comment
@ motig88 Смотрите изменения в новом финальном разделе. - person VoiceOfUnreason; 18.02.2021
comment
@VoiceOfUnreason, спасибо. Я не вижу себя предлагающим новый HTTP-глагол (по крайней мере, пока), но это говорит мне о том, что мне нужно переоценить то, как я смотрю на семантику POST и PATCH. Мой подход до сих пор был слишком наивен и не использовал весь потенциал этих глаголов. Я сделаю свою домашнюю работу и отчитаюсь, если приду к выводу. - person motig88; 18.02.2021