Что делать, когда вам нужно больше глаголов в REST - PullRequest
26 голосов
/ 22 апреля 2010

Существует еще один похожий вопрос на мой, но обсуждение отклонилось от проблемы, с которой я сталкиваюсь.

Скажем, у меня есть система, которая занимается отчетами о расходах (ER).Вы можете создавать и редактировать их, добавлять вложения и утверждать / отклонять их.

Отчет о расходах может выглядеть следующим образом:

GET /er/1
=>
{"title": "Trip to NY", "totalcost": "400 USD",
 "comments": [
   "john: Please add the total cost",
   "mike: done, can you approve it now?"
   ],
 "approvals": [
   {"john": "Pending"}, {"finance-group": "Pending"}]
}

Это выглядит хорошо, верно?Вот как выглядит отчет о расходах.

Если вы хотите обновить его, вы можете сделать это:

POST /er/1
{"title": "Trip to NY 2010"}

Если вы хотите утвердить его, вы можете сделать это:

POST /er/1/approval
{"approved": true}

Но что, если вы хотите обновить отчет и одобрить его одновременно?Как мы это делаем?Если вы хотите только одобрить, то имеет смысл сделать POST с чем-то вроде /er/1/approval.

Мы могли бы поставить флаг в URL, POST /er/1?approve=1, и отправить изменения данных как тело,но этот флаг не кажется RESTful.

Мы могли бы также добавить специальное поле для отправки, но это тоже выглядит немного странно.Если бы мы сделали это, то почему бы не отправить данные с такими атрибутами, как set_title или add_to_cost?

Мы могли бы создать новый ресурс для обновления и утверждения, но (1) я не могу думать о том, какназвать его без глаголов, и (2) кажется неправильным называть ресурс, основываясь на том, какие действия могут быть с ним выполнены (что произойдет, если мы добавим больше действий?)

У нас может быть X-Approve: True | False заголовок, но заголовки кажутся неподходящим инструментом для работы.Также было бы трудно получить заданные заголовки без использования javascript в браузере.

Мы могли бы использовать пользовательский тип носителя application/approve+yes, но это не лучше, чем создание нового ресурса.

Мы могли бы создать временный URL "пакетных операций", /er/1/batch/A.Затем клиент отправляет несколько запросов, возможно, POST /er/1/batch/A для обновления, затем POST /er/1/batch/A/approval для подтверждения, затем POST /er/1/batch/A/status для завершения пакета.На бэкэнде сервер ставит в очередь все пакетные запросы где-то, а затем обрабатывает их в той же бэкэнд-транзакции, когда получает запрос «завершить пакетную обработку».Недостатком этого, очевидно, является то, что он вносит большую сложность.

Итак, что является хорошим, общим способом решения проблемы выполнения нескольких действий в одном запросе?Общие, потому что легко представить дополнительные действия, которые могут быть выполнены в том же запросе:

  1. Подавлять или отправлять уведомления (по электронной почте, в чат, в другую систему и т. Д.)
  2. Отменить некоторую проверку(максимальная стоимость, имена участников обеда)
  3. Запуск рабочего процесса бэкэнда, который не представлен в документе.

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

Ответы [ 4 ]

24 голосов
/ 22 апреля 2010

Архитектура REST говорит, что ресурс управляется сервером и идентифицируется по URL.

В этом смысле /er/1/approval не является разумным URL-адресом или моделью для использования, если у вас нет объекта утверждения или объекта, которым вы управляете и управляете на стороне сервера. Мне кажется, сущность - это сам отчет о расходах, что означает, что /er/1 - это ваш URL-путь.

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

установить данные:

{ action: "modify", data: { purpose : "Club hopping" } }

одобрить:

{ action: "approve" }

добавить товар:

{ action:"additem", data: { amount:72.13, category:113, note:"client dinner" }}

и т.д..


С Филдинг Ch5, который определил REST ,

Параметры (запроса) состоят из данных управления запросом, идентификатора ресурса, указывающего цель запроса, и необязательного представления.

... и ...

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


Поэтому, если вы хотите выполнить несколько действий над ресурсом, вам следует встраивать в «контрольные данные» несколько сообщений или запросов действий. В моем примере опубликованные данные будут выглядеть примерно так:

{ action: "modify", data: { purpose : "Club hopping" } }
{ action: "approve" }

Но вы, вероятно, захотите обобщить это так:

{ actions: [ {action:"modify", data: {...} }, { action:"approve"} ] } 

Сообщения или действия, которые ваш сервер может обрабатывать для каждого конкретного типа объекта, определяются вами.

ps: иногда реализации REST используют HTTP PUT для создания ресурса и POST для изменения или действия с существующим ресурсом.

и: Мне понравилась статья, Как получить чашку кофе .

4 голосов
/ 22 апреля 2010

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

Итак, вы можете сделать что-то простое, например:

POST /Expenses/Approved
{ .. Expense document ... }

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

POST /ExpenseApprover/John/ApprovedExpenses
{ .. Expense document ... }

Если вам нужно представить отчет о расходах на утверждение, вы можете сделать

POST /ExpenseApprover/John/Pending
{ .. Expense document ... }

И не забывайте, что гипермедиа может включить этот рабочий процесс. Представьте, что кто-то создает первоначальный отчет о расходах, сервер может ответить следующим JSON.

{ "id" : "234",
  "title": "Trip to NY", "totalcost": "400 USD",
  "submit_url": "/ExpenseApprover/John/Pending"
}

Клиент может отправить POST в submit_url, чтобы перевести расходы на следующий шаг. Затем, когда Джон получает счет, он получает

{ "id" : "234",
  "title": "Trip to NY", "totalcost": "400 USD",
  "approve_url": "/ExpenseApprover/Finance/Pending",
  "denied_url": "/ExpenseApprover/John/Denied",
}

Когда финансовый отдел делает

GET /ExpenseApprover/Finance/Pending

они могут получить список ожидающих расходов,

{ PendingExpense: [
    { "id" : "234",
      "title": "Trip to NY", "totalcost": "400 USD",
     "approve_url": "/Expense/Approved",
     "denied_url": "/ExpenseApprover/Finance/Denied",
    }
   ]
}

Простите мой ужасный JSON, но я надеюсь, что вы поймете, что, включив ссылку в ответ, вы сможете управлять ходом вашего приложения. Вы также можете перестать беспокоиться о том, как выглядит URL, потому что клиенту на самом деле все равно. Клиент читает URL-адрес из ответа на основе имени свойства и разыменовывает его. Вы можете миллион раз передумать о том, какова лучшая структура URL, и ваши клиенты не пострадают. Только не меняйте название недвижимости!

Эти URL-адреса «корзины состояния» используются для хранения набора ресурсов, имеющих аналогичный статус. Идея в том, что вы поместите документ в коллекцию:

POST /ExpenseApprover/Finance/Denied

{"id" : "234", "title": "Trip to NY", "totalcost": "400 USD"}

Нет необходимости однозначно определять конкретные расходы, которые вы добавляете в URL, потому что основной документ должен содержать какое-то значение идентифицирующего ключа.
Этот метод также подходит для обозначения расходов, имеющих расхождения. Вы просто создаете новый ресурс, содержащий расходы с расхождениями, и публикуете в нем свой отчет о расходах.

POST /Discrepancies
{"id" : "234", "title": "Trip to NY", "totalcost": "400 USD"}
2 голосов
/ 18 февраля 2015

Что делать, когда вам нужно больше глаголов в REST

У вас есть 3 варианта:

  • создать новый ресурс и использовать доступные методы HTTP счтобы описать, что вы хотите
  • проверьте стандарт, возможно, вам не хватает уже существующего метода
  • отправьте новый RFC о вашем желаемом методе в ietf, возможно, они примут его

В вашем случае вы только что пропустили RFC метода PATCH на месяц или два.

1 голос
/ 22 апреля 2010

Я думаю, вы делаете это более сложным, чем нужно. Относитесь к своему отчету о расходах как к полному ресурсу, и любые изменения в нем - просто вопрос PUT нового представления в URI, где находится ресурс. Не нужно настраивать действия, чтобы изменить статус, просто ПОЛУЧИТЕ ресурс - внесите свои изменения - затем верните его назад. Готово.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...