在RESTful API中实现命令模式


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}

我猜应该阻止Update / PUT,因为此处不相关。

为了撤消操作,我想到了使用

DELETE /action/{action-id}

实际上将调用相关对象的Undo方法,并更改其状态。

假设我只对一次“撤消”感到满意,而无需重做。

这种方法可以吗?

有什么陷阱,不使用它的原因吗?

从客户的观点看是否明白?


简短的答案,那不是REST。
Evan Plaice 2013年

3
@EvanPlaice会详细说明吗?这正是问题所在。
Mithir

1
我会在一个答案中详细说明,但加里的答案已经涵盖了我要添加的大部分/全部内容。我说这还没有结束,因为URI仅应代表资源(即不代表操作)。通过GET / POST / PUT / DELETE / HEAD处理动作。将REST视为OOP接口。目的是使API适应一般模式,并使其与实现特定细节分离。
Evan Plaice

1
@EvanPlaice好吧,我明白了,谢谢。我认为这很令人困惑,因为Deposit可以被视为名词和动词……
Mithir 2013年

在这种情况下,URI应该表示一项事务,其中借记(取钱)和贷记(送钱)是通过POST请求完成的操作。两者都使用POST,因为每次将钱向任一方向移动时,代表新创建的交易。在您的特定情况下,交易是在持卡人的帐户上进行的,因此卡的帐号是资源URI。
Evan Plaice 2013年

Answers:


13

您正在添加一个令人困惑的抽象层

您的API开始时非常干净简单。HTTP POST使用给定的参数创建一个新的存款资源。然后,通过介绍“操作”的概念脱离轨道,该操作是实现的细节而不是API的核心部分。

作为替代方案,请考虑此HTTP对话...

POST /卡/ {卡ID} /帐户/ {帐户ID} /存款

AmountToDeposit = 100,不同的参数...

创建201

位置= /卡/ 123 /帐户/ 456 /存款/ 789

现在,您要撤消此操作(从技术上讲,在平衡的会计系统中不应允许此操作,但是嘿):

删除/ card / 123 / account / 456 / Deposit / 789

204没有内容

API使用者知道他们正在处理“存款”资源,并能够确定允许对其执行哪些操作(通常通过HTTP中的OPTIONS)。

尽管今天通过“操作”执行删除操作,但不能保证当您将此系统从C#迁移到Haskell并维护前端时,“操作”的次要概念将继续增加价值,而存款的主要概念当然可以。

编辑以涵盖删除和存款的替代方法

为了避免删除操作,但仍然有效地删除了存款,您应该执行以下操作(使用通用交易进行存款和取款):

POST /卡/ {卡ID} /帐户/ {帐户ID} /交易

Amount = -100,不同的参数...

创建201

位置= /卡/ 123 /帐户/ 456 /交易/ 790

创建一个具有完全相反数量(-100)的新事务资源。这具有平衡帐户回到0的作用,从而使原始交易无效。

您可以考虑创建一个“实用程序”端点,例如

POST / card / {card-id} / account / {account-id} / Transaction / 789 / Undo <-Bad!

获得相同的效果。但是,这通过引入动词来破坏URI的语义,使其成为标识符。您最好不要在标识符中使用名词,并且将操作限制在HTTP动词上。这样,您可以轻松地从标识符创建一个永久链接,并将其用于GET等。


3
+1“从技术上讲,在平衡的会计系统中不应允许这样做”。有人知道如何数豆。该声明绝对正确,相反的方法是进行另一笔交易,将资金退回。交易完成后,总分类帐条目应始终被视为永久不变。
Evan Plaice 2013年

因此,如果我更改了自己的问题,而不是将Delete / action / ...改为Delete / deposit / ...可以吗?
Mithir

2
@Mithir我正在描述会计规则。在标准的两次进入簿记系统中,您永远不会删除交易。历史一旦定下来,就不会改变,以使人们保持诚实。在您的情况下,您仍然可以使用DELETE操作,但在后端(例如总账数据库表)上,您将添加另一笔交易,该交易代表将钱退还(即退还)给用户。我不是bean柜台(即会计),但这是“ I会计原理”课程中教授的标准实践之一。
Evan Plaice

2
(续)数据库日志以类似方式使用事务。因此,仅使用日志就可以复制和/或重建数据集。只要按时间顺序重播事务,就应该有可能从历史记录的任何点重建数据集。从方程式中删除可变性可确保一致性。
Evan Plaice

1
公平地说,只需将其重命名为Transaction。
加里·罗

1

REST存在的主要原因是对网络错误的恢复能力。为此,所有操作应是幂等的

基本方法似乎是合理的,但是您描述DepositAction创作的方式听起来并不等效,应该固定。通过让客户端提供将用于检测重复请求的唯一ID。因此创作将变为

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

如果使用与先前相同的内容对同一URL进行另一个PUT,则如果内容相同,则响应应该仍然是,201 created如果内容不同,则响应为错误。这使客户端可以在失败时简单地重新发送请求,因为客户端无法确定请求或响应是否丢失。

使用PUT更有意义,因为它只写资源并且是幂等的,但是使用POST也不会造成任何问题。

为了查看交易细节,客户将GET使用相同的URL,即

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

并撤消它可以删除它。但是,如果它确实如示例所示与钱有关,我建议尽管添加问责制,但要添加“已取消”标志来放置它(仍然存在创建和取消的交易的痕迹)。

现在,您需要选择一种创建唯一ID的方法。您有几种选择:

  1. 在必须包含的交换中较早地发出客户端特定的前缀。
  2. 添加特殊的POST请求以从服务器获取空白的唯一ID。该请求不必是幂等的(也可以不是真的),因为未使用的ID不会真正引起任何麻烦。
  3. 只需使用UUID。每个人都使用它们,似乎没有人对基于MAC的和随机的都没有任何问题。

2
据我所知,POST不是幂等的。en.wikipedia.org/wiki/POST_(HTTP)#Affecting_server_state
Mithir

@Mithir:POST不被认为是幂等的;它仍然可以。但是,由于所有REST操作都应该是幂等的,因此POST在REST中基本上没有位置。
Jan Hudec

1
我很困惑...我已阅读的内容和我熟悉的现有实现(ServiceStack,ASP.NET Web API),都表明POST在REST中占有一席之地。
Mithir 2013年

3
在REST中,幂等性分配给资源,而不是协议或其响应代码。因此,在基于HTTP的REST中,方法GET,PUT,DELETE,PATCH等被认为是幂等的,尽管它们的响应代码可能会随后续调用而变化。从每个调用都会创建新资源的意义上讲,POST是幂等的。请参阅Fielding的POST可以使用
Gary Rowe 2013年

1
休息时允许进行非幂等的操作。该断言完全是错误的。
安迪
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.