Подтвердить что ты не робот

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

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

Скажем, у меня есть система, которая занимается отчетами о расходах (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, чтобы завершить пакет. На бэкэнд сервер останавливает все пакетные запросы где-то, а затем обрабатывает их в одной и той же бэкэнд-транзакции, когда получает запрос "окончательная пакетная обработка". Недостатком этого является, очевидно, то, что он вводит большую сложность.

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

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

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

4b9b3361

Ответ 1

Архитектура 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" }}

и т.д..


Из Fielding Ch5, который определил REST,

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

... и...

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


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

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

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

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

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

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

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

Ответ 2

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

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

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-адреса "status bucket" используются для хранения набора ресурсов, имеющих аналогичный статус. Идея состоит в том, что вы отправляете документ в коллекцию:

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"}

Ответ 3

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

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

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

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

Ответ 4

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