Каков спокойный способ представления операции клонирования ресурса в URL-адресе?

У меня есть REST API, который предоставляет сложный большой ресурс, и я хочу иметь возможность клонировать этот ресурс. Предположим, что ресурс открыт в /resources/{resoureId}

Чтобы клонировать ресурс 10, я мог бы сделать что-то вроде.

  • GET /resources/10
  • POST /resources/ body of put, содержащий дубликат представления GET /resources/10 без идентификатора, так что POST создает новый ресурс.

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

Я мог бы сделать что-то вроде POST /resources/10/clone или POST resources/clone/10, но оба этих подхода ошибочны, потому что глагол в URL-адресе.

Каков наиболее "успокоительный/нунильный" способ создания URL-адреса, который можно использовать в этом типе ситуации?

Ответ 1

Поскольку в HTTP нет метода копирования или клонирования, вам решать, что вы хотите делать. В этом случае POST кажется вполне разумным, но другие стандарты используют разные подходы:

  • В WebDAV добавлен метод COPY.
  • Amazon S3 использует PUT без тела и специальный x-amz-copy-source. Они называют это PUT Object - Copy.

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

Если ваш сервис определяет POST/resources как "создать новый ресурс", то почему бы просто не определить другой способ указать ресурс, отличный от тела POST? Например, POST/resources?source=/resources/10 с пустым телом.

Ответ 2

Ответ Фрэнсиса - отличный и, вероятно, то, что вы ищете. С учетом этого, это не технически RESTful, поскольку (как он говорит в комментариях), он полагается на клиента, предоставляющего внеполосную информацию. Поскольку вопрос был "что является спокойным способом", а не "что является хорошим способом/лучшим способом", это заставило меня задуматься о том, есть ли решение RESTful. И я думаю, что последующее решение RESTful, хотя я не уверен, что на практике это будет лучше.

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

Решение POST + sourceId создает специальный URL-адрес, который указывает не на ресурс, а на инструкцию сделать что-то. Каждый раз, когда вы обнаруживаете, что создаете специальные URL-адреса, стоит подумать над тем, можете ли вы обойти необходимость этого, просто определяя больше ресурсов.

Мы хотим иметь возможность копировать

resources/10

Что делать, если мы придумаем другой ресурс:

resources/10/copies

... и определение этого ресурса - это просто "сбор ресурсов, которые являются копиями ресурса /10".

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

Это звучит странно, но естественно вписывается в семантику REST. Например, скажем, что этот ресурс в настоящее время выглядит следующим образом (здесь я буду использовать представление JSON):

[]

Мы можем просто обновить это с помощью POST или PATCH [1]:

POST resources/copies/10
["resources/11"]

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

С помощью этого решения все четко определяется как ресурс, и все имеет один канонический URL-адрес, и клиенту никогда не требуется внеполосная информация.

В конечном счете, стоит ли идти с этим странным решением только ради того, чтобы быть более RESTful? Вероятно, это зависит от вашего индивидуального проекта. Но всегда интересно попробовать и сфокусировать проблему по-другому, создав разные ресурсы!

[1] Я не знаю, имеет ли смысл разрешать GET на "ресурсы/10/копии". Очевидно, как только исходный ресурс или его копия изменится, копия больше не является копией и не должна быть в этой коллекции. Внедрение - я не вижу смысла обременять сервер, отслеживая это, поэтому я считаю, что это следует рассматривать как ресурс только для обновления.

Ответ 3

Я думаю, что POST/resources/{id} будет хорошим решением для копирования ресурса.

Зачем?

  • POST/resources является стандартным REST-стандартом для СОЗДАНИЯ нового ресурса
  • POST/resources/{id} не должно быть возможным в большинстве API REST, потому что этот идентификатор уже существует - вы никогда не создадите новый ресурс с вами (клиентом), определяющим идентификатор. Сервер определит идентификатор.

Также обратите внимание, что вы никогда не будете копировать ресурс A на ресурс B. Поэтому, если вы хотите скопировать существующий ресурс с id = 10, некоторые ответы предлагают такие вещи:

POST /resources?sourceId=10

POST /resources?copyid=10

но это проще:

POST /resources/10

который создает копию 10 - вам нужно извлечь 10 из хранилища, поэтому, если вы не найдете его, вы не сможете скопировать его = выбросить 404 Not Found.

Если он существует, вы создаете его копию.

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

POST /resources?source=/resources/10
POST /resources-a?source=/resources-b/10

Так почему бы просто не использовать POST/resources/{id}

  • Это создаст новый ресурс
  • Копируемый родитель определяется {id}
  • Копия будет только на том же ресурсе
  • Это самый REST-подобный вариант

Что Вы думаете об этом?

Ответ 4

Будет просто поместить его там, если это может помочь кому угодно. У нас был аналогичный сценарий, в котором мы предоставляли "clone vm" как функцию для масштабирования нашего предложения IaaS. Поэтому, если пользователь хочет масштабировать, они должны были бы поразить конечную точку POST: /vms/vm101 с помощью request_body, являющегося

{"action": "clone", // Specifies action to take, since our users can do couple of other actions on a vm, like power_off/power_on etc.
 "body": {"name": [vm102, vm103, vm104] // Number of clones to make
          "storage": 50, ... // Optional parameters for specifying differences in specs one would want from the base virtual machine 
          }

и 3 клона vm101, а именно: vm102, vm103 и vm104 будут откручены.

Ответ 5

Вы хотите создать копию определенного ресурса. Мой подход в этом случае будет заключаться в использовании следующей конечной точки: POST/resources/{id}/copy, прочитайте ее "создайте копию ресурса {id}"