资源的REST API设计,其属性不可由客户端编辑

时间:2013-11-06 19:38:41

标签: api http rest

处理资源属性的最佳方法是什么,必须通过另一种未向API使用者公开的方法进行修改/更新?

示例:

  1. 请求用于X的新令牌。必须根据一组特定的业务规则/逻辑生成令牌。

  2. 在旧利率到期后请求/刷新货币的汇率。该费率仅供参考,并将用于后续交易。

  3. 请注意,在上面的两个示例中,值是资源的属性,而不是它们拥有的单独资源。

    处理这些类型的场景以及API使用者无法控制属性值但需要请求新属性的其他场景的最佳方法是什么。一种选择是允许PATCH在请求正文中使用该特定属性但不实际将属性更新为指定的值,而是运行必要的逻辑来更新属性并返回更新的资源。

    让我们更详细地看一下#1:

    请求

    GET /User/1
    

    响应

    {
       "Id": 1,
       "Email": "myemail@gmail.com",
       "SpecialToken": "12345689"
    }
    

    作为API的使用者,我希望能够请求新的SpecialToken,但生成令牌的业务规则对我来说是不可见的。

    如何在REST范例中告诉API我需要新的/刷新的SpecialToken

    有一种想法是:

    请求

    PATCH /User/1
    {
       "SpecialToken": null
    }
    

    服务器会看到此请求并知道它需要刷新令牌。后端将使用特定算法更新SpecialToken并返回更新的资源:

    响应

    {
       "Id": 1,
       "Email": "myemail@gmail.com",
       "SpecialToken": "99999999"
    }
    

    此示例可以扩展到示例#2,其中SpecialToken是资源CurrencyTrade上的汇率。 ExchangeRate是一个只读值,API的使用者不能直接更改,但可以请求更改/刷新它:

    请求

    GET /CurrencyTrade/1
    

    响应

    {
       "Id": 1,
       "PropertyOne": "Value1",
       "PropertyTwo": "Value2",
       "ExchangeRate":  1.2
    }
    

    使用API​​的人需要一种方法来请求新的ExchangeRate,但是他们无法控制值是什么,它只是read only property

5 个答案:

答案 0 :(得分:4)

您真正处理的是资源的两种不同表示:一种是客户端可以通过POST / PUT发送的内容,另一种是服务器可以返回的内容。你处理资源本身。

能够更新令牌有哪些要求?什么是令牌?可以根据User中的其他值计算令牌吗?这可能只是一个例子,但上下文将推动您最终构建系统的方式。

除非有要求禁止它,否则我可能会通过使用PUT“触摸”资源表示来实现令牌生成场景。据推测,客户端无法更新Id字段,因此不会在客户端的表示中定义。

请求

PUT /User/1 HTTP/1.1
Content-Type: application/vnd.example.api.client+json

{
   "Email": "myemail@gmail.com"
}

<强>响应

200 OK
Content-Type: application/vnd.example.api.server+json

{
   "Id": 1,
   "Email": "myemail@gmail.com",
   "SpecialToken": "99999999"
}

从客户端的角度来看,Email是唯一可变的字段,因此这表示客户端向服务器发送消息时资源的完整表示。由于服务器的响应包含其他不可变信息,因此它实际上发送了相同资源的不同表示。 (令人困惑的是,在现实世界中,你通常不会看到如此清晰地说出的媒体类型......它通常包含在像application / json这样模糊的东西中。)

对于您的汇率示例,我不明白为什么客户必须告诉服务器汇率是陈旧的。如果客户比服务器更了解汇率的新鲜度,并且服务器正在提供价值,那么这不是一个非常好的服务。 :)但是,在这样的场景中,我会“触摸”资源,就像我在用户场景中所做的那样。

答案 1 :(得分:2)

有很多方法可以解决这个问题。我会说最好的一个可能是/User/1/SpecialToken资源,它会给202 Accepted一条消息,说明资源无法完全删除,并且每当有人试图时都会刷新。然后,您可以使用DELETE,使用PUT替换为空值,甚至使用PATCH直接替换为SpecialToken或User的属性。尽管有人提到过,但在用户资源中保留SpecialToken值并没有错。客户端不必执行两个请求。

@AndyDennie建议的方法,对TokenRefresher资源的POST也很好,但我更喜欢其他方法,因为它感觉不像是一个自定义的行为。一旦在您的文档中明确表示该资源无法删除且服务器只是刷新它,客户端就知道他可以删除或使用任何标准化操作将其设置为null以便刷新它。

请记住,在真正的RESTful API中,用户的超媒体表示只会有一个标记为“刷新令牌”的链接,无论执行什么操作,URI的语义都无关紧要。

答案 2 :(得分:1)

我认为您应该考虑将SpecialToken作为资源,并允许API的{@ 1}}的使用者检索新实例。不知何故,您需要将POST资源链接到User资源。请记住,REST的核心原则之一是您不应该依赖于带外信息,因此如果您想要坚持这一点,那么您将需要调查使用链接的可能性。

首先,让我们看看你得到了什么:

请求:

SpecialToken

响应:

GET /User/1
Accept: application/json

虽然此响应确实包含对象中的200 OK Content-Type: application/json { "Id": 1, "Email": "myemail@gmail.com", "SpecialToken": "12345689" } 属性,但因为SpecialTokenContent-Type对于未编程以理解此特定对象结构的客户端实际上并不意味着什么。只了解JSON的客户端会将此作为对象与其他任何对象一样。我们暂时忽略它。我们只想说我们为application/json字段使用不同的资源;它可能看起来像这样:

请求:

SpecialToken

响应:

GET /User/1/SpecialToken
Accept: application/json

因为我们做了200 OK Content-Type: application/json { "SpecialToken": "12345689" } ,所以理想情况下进行此调用不应该修改资源。然而,GET方法不遵循相同的语义。实际上,向该资源发出POST消息可能会返回不同的主体。那么让我们考虑以下几点:

请求:

POST

响应:

POST /User/1/SpecialToken
Accept: application/json

请注意200 OK Content-Type: application/json { "SpecialToken": "98654321" } 消息不包含正文。这似乎是非常规的,但HTTP规范并没有禁止这一点,实际上W3C TAG说它是all right

  

请注意,即使不在HTTP消息正文中提供数据,也可以使用POST。在这种情况下,资源是URI可寻址的,但POST方法向客户端指示交互不安全或可能有副作用。

听起来对我来说是正确的。回到那一天,我听说有些服务器在没有机构的情况下遇到了POST消息的问题,但我个人对此没有任何问题。只需确保POST标题设置正确,您应该是金色的。

因此,考虑到这一点,这似乎是一种完全有效的方式(根据REST)来做你所建议的。但是,请记得之前我提到有关JSON的内容实际上并没有任何应用程序级别的语义?好吧,这意味着为了让您的客户端实际上首先发送Content-Length来获取新的POST,它需要知道该资源的URL,或者至少知道如何制作这样的资源一个URL。这被认为是一种不好的做法,因为它将客户端绑定到服务器。我们来说明一下。

鉴于以下要求:

SpecialToken

如果服务器不再识别URL POST /User/1/SpecialToken Accept: application/json ,它可能会返回404或其他相应的错误消息,并且您的客户端现在已损坏。要修复它,您需要更改负责的代码。这意味着您的客户端和服务器不能彼此独立地进化,并且您已经引入了耦合。但是,修复此问题可能相对容易,前提是您的客户端HTTP例程允许您检查标头。在这种情况下,您可以引入消息链接。让我们回到我们的第一个资源:

请求:

/User/1/SpecialToken

响应:

GET /User/1
Accept: application/json

现在在响应中,标题中指定了链接。这一点添加意味着您的客户不再需要知道如何访问200 OK Content-Type: application/json Link: </User/1/SpecialToken>; rel=token { "Id": 1, "Email": "myemail@gmail.com", "SpecialToken": "12345689" } 资源,它可以只关注链接。虽然这不会解决所有耦合问题(例如,SpecialToken不是registered link relation),但它确实有很长的路要走。您的服务器现在可以随意更改token网址,您的客户端无需更改即可正常运行。

这是HATEOAS的一个小例子,是Hypermedia As Application Of Application State的简称,这实际上意味着您的应用程序会发现如何做事而不是事先了解它们。首字母缩略词部门的某人确实被解雇了。为了满足您对此主题的兴趣,有一个really cool talk by Jon Moore显示了一个广泛使用超媒体的API。超媒体的另一个很好的介绍是writings of Steve Klabnik。这应该可以帮到你。

希望这有帮助!

答案 3 :(得分:1)

另一个想法刚刚发生在我身上。您可以简单地将现有的特殊标记POST到与此User相关联的RevokedTokens集合(假设在给定时间每个用户只允许一个特殊标记),而不是模拟RefreshToken资源。

请求

GET /User/1
Accept: application/hal+json

<强>响应

200 OK
Content-Type: application/hal+json

{
   _links: {
     self: { href: "/User/1" },
     "token-revocation": { href: "/User/1/RevokedTokens" }
   },
   "Id": 1,
   "Email": "myemail@gmail.com",
   "SpecialToken": "12345689"
}

在令牌撤销关系和POST之后,现有的特殊令牌将如下所示:

请求

POST /User/1/RevokedTokens
Content-Type: text/plain

123456789

<强>响应:     202接受(或204无内容)

然后,用户的后续GET将为其分配新的特殊标记:

请求

GET /User/1
Accept: application/hal+json

<强>响应

200 OK
Content-Type: application/hal+json

{
   _links: {
     self: { href: "/User/1" },
     "token-revocation": { href: "/User/1/RevokedTokens" }
   },
   "Id": 1,
   "Email": "myemail@gmail.com",
   "SpecialToken": "99999999"
}

这具有对可以影响其他资源的实际资源(令牌撤销列表)建模的优势,而不是将服务建模为资源(即,令牌刷新资源)。

答案 4 :(得分:0)

负责刷新用户资源中令牌的单独资源怎么样?

POST /UserTokenRefresher
{
    "User":"/User/1"
}

这可以在响应中返回刷新的用户表示(使用新令牌)。