4

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

例子:

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

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

请注意,在上述两个示例中,这些值是资源的属性,而不是独立的资源。

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

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

要求

GET /User/1

回应

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

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

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

一个想法是这样做:

要求

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

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

回应

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

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

要求

GET /CurrencyTrade/1

回应

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

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

4

5 回答 5

4

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

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

除非有禁止它的要求,否则我可能会通过使用 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 之类的模糊内容中)。

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

于 2013-11-06T21:08:37.167 回答
2

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

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

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

于 2013-11-06T23:42:55.853 回答
1

我认为您应该考虑创建SpecialToken一个资源,并允许 api 的使用者使用POST它来检索一个新实例。不知何故,您需要将资源链接UserSpecialToken资源。请记住,REST 的中心原则之一是您不应该依赖带外信息,因此如果您想忠于这一点,您将需要调查使用链接的可能性。

首先,让我们看看你有什么:

要求:

GET /User/1
Accept: application/json

回复:

200 OK
Content-Type: application/json


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

尽管此响应确实包含SpecialToken对象中的属性,但Content-Type对于application/json未编程为理解此特定对象结构的客户端而言,它实际上并没有任何意义。只理解 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"
}

请注意POST消息如何不包含正文。这可能看起来不合常规,但 HTTP 规范并没有禁止这样做,事实上 W3C TAG 说没关系

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

听起来对我来说是正确的。过去,我听说一些服务器在POST没有正文的消息方面存在问题,但我个人对此没有任何问题。只要确保Content-Length标题设置正确,你就应该是金色的。

因此,考虑到这一点,这似乎是一种完全有效的方式(根据 REST)来执行您的建议。但是,还记得之前我提到过关于 JSON 实际上没有任何应用程序级语义的内容吗?好吧,这意味着为了让您的客户端真正发送 aPOST以获取新SpecialToken资源,它需要知道该资源的 URL,或者至少知道如何制作这样的 URL。这被认为是一种不好的做法,因为它将客户端与服务器联系在一起。让我们来说明一下。

鉴于以下要求:

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

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

要求:

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资源,它只需点击链接即可。虽然这并不能解决所有耦合问题(例如,token不是注册的链接关系),但它确实有很长的路要走。您的服务器现在可以随意更改SpecialTokenURL,您的客户端无需更改即可工作。

这是 HATEOAS 的一个小例子,是 Hypermedia As The Engine Of Application State 的缩写,本质上意味着您的应用程序会发现如何做事,而不是预先知道它们。首字母缩略词部门的某个人确实因此而被解雇。为了满足您对这个主题的兴趣,Jon Moore 有一个非常酷的演讲,展示了一个广泛使用超媒体的 API。另一个很好的超媒体介绍是Steve Klabnik 的著作。这应该让你开始。

希望这可以帮助!

于 2013-11-07T00:48:33.917 回答
1

我突然想到了另一个想法。您可以简单地将现有的特殊令牌发布到与此用户关联的 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 /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"
}

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

于 2013-11-07T22:14:16.667 回答
0

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

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

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

于 2013-11-06T22:47:45.270 回答