以RESTful方式向资源属性添加内容

6
这是使用POST方法RESTfully更新值的后续。
我该如何使用REST简单地追加资源属性?假设我有customer.balance,balance是一个整数。我只想告诉服务器将5添加到当前余额中。我可以用RESTful方式实现吗?如果可以,怎么做?
请记住,客户端不知道客户的现有余额,因此它不能只是:
1.获取客户 2.customer.balance += 5 3.发布客户
(上述方法还存在并发问题。)

1
我很高兴你问了这个问题,尽管我有点失望它没有得到太多的关注。我想知道更有经验的人对这个话题的看法。 - Rob Hruska
3个回答

4

简单但有点丑陋:

这是我回答你其他问题的的一个更简单的变体。

我认为如果你按照以下步骤进行,仍然符合REST的限制。然而,我也很想知道其他人对这种情况的看法,所以希望能听到其他人的意见。

你的URI将是:

/customer/21/credits

你可以将一个信用资源(可能是<credit>5</credit>)通过POST请求发送到URI,服务器可以获取客户的余额并将其与提供的值相加。此外,你还可以支持负数信用额度(例如<credit>-10</credit>);
请注意,/customer/21/credits不必支持所有方法,只支持POST即可。
但是,如果信用额度在你的系统中不是真正的资源,则会变得有点奇怪。HTTP规范指出:

如果一个资源已经在原始服务器上创建,则响应应该是201(已创建),并包含一个描述请求状态和引用新资源的实体以及一个位置标头。

技术上说,你在这里并没有创建一个资源,而是向客户的余额中添加(这实际上是系统中所有以前信用额度的聚合)。由于你没有保留信用额度(假设),你实际上无法返回对新“创建”的信用额度资源的引用。你可能可以返回客户的余额或<customer>本身,但这对客户端来说有点不直观。这就是为什么我认为将每个信用额度视为系统中的新资源更容易处理(请参见下文)。

我更喜欢的解决方案:

这是我在你其他问题中的答案改编而来。在这里,我将尝试从客户端/服务器正在执行的角度来考虑:

  • Client:

    1. Builds a new credit resource:

      <credit>
        <amount>5</amount>
      </credit>
      
    2. POSTs resource to /customer/21/credits

      POSTing here means, "append this new <credit> I'm providing to the list of <credit>s for this customer.

  • Server:

    1. Receives POST to /customer/21/credits
    2. Takes the amount from the request and +=s it to the customer's balance
    3. Saves the credit and its information for later retrieval
    4. Sends response to client:

      <credit href="/customer/21/credits/credit-id-4321234">
        <amount>5</amount>
        <date>2009-10-16 12:00:23</date>
        <ending-balance>45.03</ending-balance>
      </credit>
      
这将给您带来以下优势:
  • Credits can be accessed at a later date by id (with GET /customer/21/credits/[id])
  • You have a complete audit trail of credit history
  • Clients can, if you support it, update or remove credits by id (with PUT or DELETE)
  • Clients can retrieve an ordered list of credits, if you support it; e.g. GET /customer/21/credits might return:

    <credits href="/customer/21/credits">
       <credit href="/customer/21/credits/credit-id-7382134">
         <amount>13</amount>
         ...
       </credit>
       <credit href="/customer/21/credits/credit-id-134u482">
         ...
       </credit>
       ...
    </credits>
    
  • Makes sense, since the customer's balance is really the end result of all credits applied to that customer.

你的方法在我看来似乎是最合乎逻辑的。 - Darrel Miller
@Darrel - 谢谢。我想知道你是否有其他选择。你的标签表明你对REST有很大的兴趣,而且你可能比我更有经验。有什么建议或改进吗? - Rob Hruska
1
问题在于这个问题的要求相对较为琐碎,因此根据我们所拥有的信息,我实际上没有什么可以补充的。实际情况往往会变得更加棘手,但是通过将POST发送到一个“更改请求”列表的子资源,可以解决REST中的许多问题,这一点非常惊人。如果您更加现实地看待这种情况,您会发现“向客户记账”是一种简化。您可能希望将其POST到类似于/Customer/21/AccountBalance/Credits之类的东西。但这超出了原始问题的范围。 - Darrel Miller
1
一种不同的方法是将“客户付款”和“客户购买”建模为完全独立的资源,并使“客户”的余额属性成为只读计算字段。 - Darrel Miller
我同意,我认为这些方法提供了更多的灵活性和控制,并且可能使其更容易在未来发展。感谢您的建议。 - Rob Hruska

0

嗯,除了@Rob-Hruska的解决方案之外,还有其他选择。

基本思路是相同的:将每个借记/贷记操作视为独立的交易。但是我曾经使用过一种后端,支持在json中存储无模式数据,因此我最终定义了API为带动态字段名的PUT。类似于这样:

PUT /customer/21

{"transaction_yyyymmddHHMMSS": 5}

我知道在“信用/借记”上下文中这是不合适的,因为活跃账户可能有不断增长的交易记录。但在我的情况下,我使用这种策略来存储有限的数据(实际上,在驾车旅行期间,我正在存储不同批次的GPS路标)。

缺点:此API风格严重依赖于后端行为的无模式特性。

优点:从语义角度来看,至少我的方法是完全符合RESTful的。

相比之下,@Rob-Hruska的“简单而稍微丑陋”的解决方案1没有有效的位置头返回“201 Created”响应,这不是常见的RESTful行为。(或者,也许我们可以让@Rob-Hruska的解决方案1返回一个虚拟的位置头,指向“410 Gone”或“404 Not Found”页面。这更符合RESTful吗?欢迎评论!)


0
以REST-ful的方式思考,您需要将动作本身视为资源。例如,如果这是银行业务,您想要更新账户余额,则应创建一个存款资源,然后添加其中一个。这样做的后果就是会更新客户的余额。
这也有助于处理并发问题,因为您将提交+5个操作而不是要求事先了解客户余额。此外,您还可以召回该资源(例如ID为51的存款/ 51)并查看有关其它详细信息(例如存款原因、存款日期等)。
编辑:意识到使用ID为5的存款实际上会混淆问题,因此将其更改为51。

所以我可以创建以下内容:customer/21/debits customer/21/credits每个都将有一组借方和贷方资源?现在,在幕后,我只想将信用额度+=,并将借方-=到customer.balance。这意味着无法获取customer/21/credit/5。从REST的角度来看,这样做是否可以?根据您的答案,似乎不行。 - pondermatic
要创建一个新的借记,我会向customer/21/debits/new发送POST请求,并在提交中包含5作为金额。如果我想获取id为5的借记,我会GET customer/21/debit/5。 - Deeksy
那应该用PUT而不是POST来创建新资源。 - Deeksy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接