Реализация шаблона команды в API RESTful

12

Я нахожусь в процессе разработки HTTP API, надеюсь, сделать его максимально RESTful.

Есть некоторые действия, функциональность которых распространяется на несколько ресурсов, и иногда их нужно отменить.

Я подумал, что это звучит как шаблон команды, но как я могу смоделировать его в ресурс?

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

POST /card/{card-id}/account/{account-id}/Deposit
AmountToDeposit=100, different parameters...

это фактически создаст новый DepositAction и активирует его метод Do / Execute. В этом случае возвращение статуса 201 Created HTTP означает, что действие было успешно выполнено.

Позже, если клиент хочет посмотреть детали действия, он может

GET /action/{action-id}

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

И чтобы отменить действие, я подумал об использовании

DELETE /action/{action-id}

который на самом деле вызовет метод Undo соответствующего объекта и изменит его статус.

Допустим, я доволен только одним Do-Undo, мне не нужно повторять.

Этот подход в порядке?

Есть ли подводные камни, причины не использовать его?

Это понятно из POV клиентов?

Mithir
источник
Короткий ответ, это не ОТДЫХ.
Эван Плейс,
3
@EvanPlaice хотите уточнить это? это точно вопрос.
Mithir
1
Я бы уточнил ответ, но ответ Гэри уже охватывает большую часть всего, что я бы добавил. Я говорю, что это не покой, потому что URI должны представлять только ресурсы (то есть не действия). Действия обрабатываются через GET / POST / PUT / DELETE / HEAD. Думайте о REST как оОП-интерфейсе. Цель состоит в том, чтобы API соответствовал общему шаблону и по возможности отделил его от конкретных деталей реализации.
Эван Плейс
1
@EvanPlaice Хорошо, я понимаю, спасибо. Я думаю, что это сбивает с толку, потому что Депозит можно рассматривать как существительное и как глагол ...
Mithir
В этом случае URI должен представлять транзакцию, в которой дебетование (получение денег) и кредитование (предоставление денег) - это действия, выполняемые с помощью запросов POST. POST используется для обоих, потому что каждый раз, когда деньги перемещаются в любом направлении, они представляют новую создаваемую транзакцию. В вашем конкретном случае транзакции происходят на счету владельца карты, поэтому номер счета карты является URI ресурса.
Эван Плейс,

Ответы:

13

Вы добавляете в слой абстракции, который сбивает с толку

Ваш API начинается очень чисто и просто. HTTP POST создает новый ресурс Deposit с заданными параметрами. Затем вы сходите с пути, представляя идею «действий», которые являются деталями реализации, а не основной частью API.

В качестве альтернативы рассмотрим этот HTTP-разговор ...

POST / card / {card-id} / account / {account-id} / Депозит

AmountToDeposit = 100, разные параметры ...

201 СОЗДАН

Местонахождение = / карты / 123 / счет / 456 / Депозиты / 789

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

УДАЛИТЬ / карта / 123 / счет / 456 / депозит / 789

204 НЕТ СОДЕРЖАНИЯ

Потребитель API знает, что он имеет дело с ресурсом Deposit, и может определить, какие операции над ним разрешены (обычно через OPTIONS в HTTP).

Хотя реализация операции удаления сегодня осуществляется с помощью «действий», нет никакой гарантии, что при переносе этой системы, скажем, с C # на Haskell и поддержке внешнего интерфейса, вторичная концепция «действия» будет продолжать увеличивать ценность. в то время как основная концепция депозита, безусловно, делает.

Изменить, чтобы покрыть альтернативу УДАЛИТЬ и Депозит

Чтобы избежать операции удаления, но, тем не менее, эффективно удалить депозит, вы должны сделать следующее (используя общую транзакцию для учета пополнения и снятия):

POST / card / {card-id} / account / {account-id} / Транзакция

Сумма = -100 , разные параметры ...

201 СОЗДАН

Местонахождение = / карты / 123 / счет / 456 / транзакция / 790

Создается новый ресурс транзакции, который имеет абсолютно противоположную сумму (-100). Это приводит к балансу счета до 0, отменяя исходную транзакцию.

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

POST / card / {card-id} / account / {account-id} / Transaction / 789 / Undo <- ПЛОХО!

чтобы получить тот же эффект. Однако это нарушает семантику URI как идентификатора, вводя глагол. Вам лучше придерживаться имен существительных в идентификаторах и ограничивать операции с помощью глаголов HTTP. Таким образом, вы можете легко создать постоянную ссылку из идентификатора и использовать ее для GET и так далее.

Гэри Роу
источник
3
+1 «технически это не должно быть разрешено в сбалансированной системе учета». Кто-то знает, как считать бобы. Это утверждение абсолютно верно, и обратный путь может заключаться в создании еще одной транзакции для зачисления средств обратно. Записи главной книги всегда должны считаться неизменными и постоянными после завершения транзакции.
Эван Плейс,
Так что, если я изменю в своих вопросах вместо Delete / action / ... на Delete / deposit / ... это нормально?
Mithir
2
@Mithir Я описывал правило учета. В стандартной двойной бухгалтерии вы никогда не удаляете транзакции. Однажды совершенная история считается неизменной, чтобы сохранять честность людей. В вашем случае вы все равно могли бы использовать действие DELETE, но в бэкэнде (например, в таблице базы данных главной книги) вы добавили бы еще одну транзакцию, представляющую кредитование (т.е. возврат) денег обратно пользователю. Я не бобовый (т. Е. Бухгалтер), но это одна из стандартных практик, преподаваемых в курсе «Принципы бухгалтерского учета I».
Эван Плейс
2
(продолжение) Журналы базы данных используют транзакции аналогичным образом. Вот почему можно копировать и / или перестраивать набор данных, используя только журналы. Пока транзакции воспроизводятся в хронологическом порядке, должна быть возможность перестроить набор данных из любой точки его истории. Удаление изменчивости из уравнения обеспечивает последовательность.
Эван Плейс
1
Достаточно справедливо, просто переименуйте его в Transaction.
Гари Роу
1

Основная причина существования REST - устойчивость к сетевым ошибкам. Для этого все операции должны быть идемпотентными .

Базовый подход кажется разумным, но то, как вы описываете DepositActionтворение, не кажется идемпотентным, что следует исправить. Имея клиента уникального идентификатора, который будет использоваться для обнаружения дублирующих запросов. Таким образом, создание изменится на

PUT /card/{card-id}/account/{account-id}/Deposit/{action-id}
AmountToDeposit=100, different parameters...

Если другой PUT с тем же URL-адресом сделан с тем же содержимым, что и ранее, ответ все равно должен быть, 201 createdесли контент такой же, и ошибка, если контент другой. Это позволяет клиенту просто повторно передать запрос в случае сбоя, поскольку клиент не может определить, был ли потерян запрос или ответ.

Имеет больше смысла использовать PUT, потому что он просто записывает ресурс и является идемпотентом, но использование POST также не вызовет никаких проблем.

Чтобы посмотреть детали транзакции, клиент будет иметь GETтот же URL, т.е.

GET /card/{card-id}/account/{account-id}/Deposit/{action-id}

и отменить его, он может удалить его. Но если это на самом деле имеет какое-либо отношение к деньгам, как предлагает образец, я бы предложил вместо этого ставить PUTting с добавленными «отмененными» флагами, хотя и для подотчетности (что остается след от созданной и отмененной транзакции).

Теперь вам нужно выбрать метод создания уникального идентификатора. У вас есть несколько вариантов:

  1. Выдать клиентский префикс ранее в обмен, который должен быть включен.
  2. Добавьте специальный запрос POST, чтобы получить пустой уникальный идентификатор с сервера. Этот запрос не должен быть идемпотентным (и на самом деле не может), потому что неиспользуемые идентификаторы не вызывают никаких проблем.
  3. Просто используйте UUID. Все используют их, и ни у кого, кажется, нет никаких проблем ни с MAC, ни со случайными.
Ян Худек
источник
2
Из того, что я знаю, POST не идемпотент. en.wikipedia.org/wiki/POST_(HTTP)#Affecting_server_state
Mithir
@Mithir: POST не считается идемпотентным; это все еще может быть. Но это правда, что, поскольку все операции REST должны быть идемпотентными, POST практически не имеет места в REST.
Ян Худек
1
Я в замешательстве ... содержимое, которое я прочитал, и существующая реализация, с которой я знаком (ServiceStack, ASP.NET Web API), все предполагают, что POST имеет место в REST.
Mithir
3
В REST идемпотентность назначается ресурсу, а не протоколу или его кодам ответа. Таким образом, в REST по HTTP методы GET, PUT, DELETE, PATCH и т. Д. Считаются идемпотентными, хотя их коды ответов могут различаться для последующих вызовов. POST является идемпотентом в том смысле, что каждый вызов создает новый ресурс. См Филдинг Это нормально использовать POST .
Гэри Роу
1
Операции, которые не являются идемпотентными, разрешены в покое. Это утверждение совершенно неверно.
Энди