60

我的应用程序有一个资源/foo。通常,它由如下所示的 HTTP 响应负载表示:

{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}

客户端并不总是需要该对象的所有四个成员。客户端告诉服务器它在表示中需要什么的RESTful 语义方式是什么?例如,如果它想要:

{"a": "some text", "b": "some text", "d": "some text"}

应该怎么做GET呢?一些可能性(如果我误解了 REST,我正在寻找更正):

  • GET /foo?sections=a,b,d.
    • 查询字符串(毕竟称为查询字符串)似乎意味着“找到符合此条件的资源并告诉我它们”,而不是“根据此自定义向我表示此资源”。
  • GET /foo/a+b+d如果 REST 语义没有涵盖这个问题,我最喜欢它,因为它很简单。
    • 打破 URI 不透明度,违反 HATEOAS。
    • 似乎打破了资源(URI 的唯一含义是标识一个资源)和表示之间的区别。但这是值得商榷的,因为它与/widgets表示一个可展示的/widget/<id>资源列表是一致的,而我从来没有遇到过这个问题。
  • 放松我的约束,响应等,并让客户端根据它想要 GET /foo/a的组件发出请求。/foo
    • 增加开销,如果/foo有数百个组件并且客户端需要其中的 100 个,这可能会成为一场噩梦。
    • 如果我想支持 的 HTML 表示/foo,我必须使用 Ajax,如果我只想要一个可以被极简浏览器抓取、呈现等的 HTML 页面,这是有问题的。
    • 为了维护 HATEOAS,它还需要到那些“子资源”的链接存在于其他表示中,可能在/foo{"a": {"url": "/foo/a", "content": "some text"}, ...}
  • GET /fooContent-Type: application/json{"sections": ["a","b","d"]}在请求正文中。
    • 不可标记且不可缓存。
    • HTTP 没有为GET. 这是合法的 HTTP,但我如何保证某些用户的代理不会从GET请求中删除正文?
    • 我的REST 客户端不允许我在GET请求中添加正文,因此我无法将其用于测试。
  • 自定义 HTTP 标头:Sections-Needed: a,b,d
    • 如果可能的话,我宁愿避免使用自定义标题。
    • 不可标记且不可缓存。
  • POST /foo/requestsContent-Type: application/json{"sections": ["a","b","d"]}在请求正文中。收到一个201. Location: /foo/requests/1然后GET /foo/requests/1接收所需的表示/foo
    • 笨重;需要来回和一些看起来很奇怪的代码。
    • Unbookmarkable and uncacheable since/foo/requests/1只是一个别名,只能使用一次,并且只保留直到被请求。
4

5 回答 5

14

我会建议查询字符串解决方案(您的第一个)。您反对其他替代方案的论点是很好的论点(以及我在尝试解决同一问题时在实践中遇到的论点)。特别是,“放松约束/响应foo/a”解决方案可以在有限的情况下工作,但从实现和使用两方面都给 API 带来了很多复杂性,并且根据我的经验,这并不值得付出努力。

我将用一个常见的例子来弱弱地反驳你的“似乎意味着”论点:考虑资源是一个大的对象列表(GET /Customers)。对这些对象进行分页是完全合理的,并且使用查询字符串来做到这一点是司空见惯的:GET /Customers?offset=100&take=50例如。在这种情况下,查询字符串不会过滤列出对象的任何属性,而是为对象的子视图提供参数。

更具体地说,我想说您可以通过使用查询字符串的这些标准来保持一致性和 HATEOAS:

  • 返回的对象应该与从不带查询字符串的 Url 返回的对象相同。
  • 没有查询字符串的 Uri 应该返回完整的对象 - 任何视图的超集,在一个 Uri 中具有查询字符串。因此,如果您缓存未修饰的 Uri 的结果,您就知道您拥有完整的实体。
  • 给定查询字符串返回的结果应该是确定性的,这样带有查询字符串的 Uris 就可以轻松缓存

但是,这些 Uris 的返回值有时会带来更复杂的问题:

  • 为仅通过查询字符串不同的 Uris 返回不同的实体类型可能是不可取的(/foo是实体但是foo/a字符串);另一种方法是返回一个部分填充的实体
  • 如果您确实对子查询使用不同的实体类型,那么如果您/foo没有a,则404状态会产生误导(/foo 确实存在!),但空响应可能同样令人困惑
  • 返回部分填充的实体可能是不可取的,但返回实体的一部分可能是不可能的,或者可能更令人困惑
  • 如果你有一个强大的模式,返回一个部分填充的实体可能是不可能的(如果a是强制性的,但客户端只请求b,你被迫返回一个垃圾值a,或者一个无效的对象)

过去,我试图通过定义所需实体的特定命名“视图”并允许查询字符串?view=summary?view=totalsOnly- 限制排列的数量来解决此问题。这也允许定义对服务消费者“有意义”的实体子集,并且可以记录在案。

归根结底,我认为这归结为一致性问题比什么都重要:您可以使用查询字符串相对容易地满足 HATEOAS 指导,但是您所做的选择需要在您的 API 中保持一致,并且我会说,有据可查。

于 2013-04-29T08:15:20.717 回答
8

我已经决定以下几点:

支持少数成员组合:我会为每个组合想出一个名称。例如,如果一篇文章有​​作者、日期和正文的成员,/article/some-slug将返回所有成员,并且/article/some-slug/meta只返回作者和日期。

支持多种组合:我将用连字符分隔成员名称:/foo/a-b-c.

404无论哪种方式,如果该组合不受支持,我将返回 a 。

架构约束

休息

识别资源

从REST的定义来看:

资源R是一个随时间变化的隶属函数M R ( t ),它在时间t映射到一组等效的实体或值。集合中的值可以是资源表示和/或资源标识符。

表示是 HTTP 正文,标识符是 URL。

这是至关重要的。标识符只是与其他标识符和表示相关联的值。这与标识符→表示映射不同。服务器可以将它想要的任何标识符映射到任何表示,只要两者都由相同的资源关联。

由开发人员通过考虑诸如“用户”和“帖子”之类的类别来提出合理描述业务的资源定义。

仇恨

如果我真的关心完美的 HATEOAS,我可以在/foo表示中的某处放置一个超链接到/foo/members,并且该表示将只包含一个指向每个受支持的成员组合的超链接。

HTTP

从 URL 的定义

查询组件包含非分层数据,与路径组件中的数据一起,用于标识 URI 方案和命名权限(如果有)范围内的资源。

所以/foo?sections=a,b,d/foo?sections=b是不同的标识符。但是它们可以在同一资源内关联,同时映射到不同的表示。

HTTP 的404代码意味着服务器找不到将 URL 映射到的任何内容,而不是 URL 没有与任何资源相关联。

功能性

没有浏览器或缓存会遇到斜杠或连字符的问题。

于 2013-04-30T02:52:10.247 回答
6

实际上,这取决于资源的功能。例如,如果资源代表一个实体:

/customers/5

这里的“5”代表客户的id

回复:

{
   "id": 5,
   "name": "John",
   "surename": "Doe",
   "marital_status": "single",
   "sex": "male",
   ...
}

因此,如果我们仔细研究一下,每个 json 属性实际上代表了客户资源实例上记录的一个字段。假设消费者想要获得部分响应,意思是部分字段。我们可以将其视为消费者希望能够通过请求选择各种字段,这对他来说是有趣的,但不是更多(为了节省流量或性能,如果部分字段难以计算) .

我认为在这种情况下,最易读和正确的 API 将是(例如,只获取namesurename

/customers/5?fields=name,surename

回复:

{
   "name": "John",
   "surename": "Doe"
}

HTTP/1.1

  • 如果请求了非法的字段名 - 返回 404 (Not Found)
  • 如果请求不同的字段名称 - 将生成不同的响应,这也与缓存一致。
  • 缺点:如果请求相同的字段,但字段之间的顺序不同(例如:fields=id,namefields=name,id),虽然响应相同,但这些响应将被单独缓存。

仇恨

  • 在我看来,纯 HATEOAS 不适合解决这个特殊问题。因为为了实现这一点,您需要为字段组合的每个排列提供一个单独的资源,这太过分了,因为它使 API 大量膨胀(假设您在一个资源中有 8 个字段,您将需要在此处输入图像描述排列!)。
  • 如果您仅对字段而不是所有排列的资源进行建模,则它会影响性能,例如,您希望将往返次数降至最低。
于 2015-03-27T10:25:12.457 回答
2

you could use a second vendor media-type in the request header application/vnd.com.mycompany.resource.rep2, you can't bookmark this however, query-parameters are not cacheable (/foo?sections=a,b,c) you could take a look at matrix-parameters however regarding this question they should be cacheable URL matrix parameters vs. request parameters

于 2013-04-29T08:23:12.100 回答
2

如果 a、b、c 是诸如 admin 之类的资源的属性,则正确的方法是使用您建议的第一种方法, GET /foo?sections=a,b,d因为在这种情况下,您将对foo集合应用过滤器。否则,如果 a、b 和 c 是单个foo集合资源,则遵循的方式是执行一系列GET请求/foo/a /foo/b /foo/c。正如您所说,这种方法具有很高的请求负载,但它是遵循 Restfull 方法的正确方法。我不会使用你提出的第二个建议,因为 url 中的 plus char 具有特殊含义。

另一个建议是放弃使用 GET 和 POST 并为foo集合创建一个操作,如下所示:/foo/filter/foo/selection任何表示集合上的操作的动词。通过这种方式,拥有一个发布请求正文,您可以传递您想要的资源的 json 列表。

于 2013-04-29T08:04:29.040 回答