同一资源的不同RESTful表示

Jor*_*dan 50 api rest url web-applications http

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

{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}
Run Code Online (Sandbox Code Playgroud)

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

{"a": "some text", "b": "some text", "d": "some text"}
Run Code Online (Sandbox Code Playgroud)

应该怎么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 /foo,Content-Type: application/json{"sections": ["a","b","d"]}在请求正文中.
    • 不可指定和不可缓存.
    • HTTP没有定义主体语义GET.这是合法的HTTP,但我如何保证某些用户的代理不会从GET请求中剥离正文?
    • 我的REST客户端不会让我在GET请求上放置一个正文,所以我不能用它进行测试.
  • 自定义HTTP标头: Sections-Needed: a,b,d
    • 如果可能的话,我宁愿避免自定义标题.
    • 不可指定和不可缓存.
  • POST /foo/requests,Content-Type: application/json{"sections": ["a","b","d"]}在请求正文中.收到一个201Location: /foo/requests/1.然后GET /foo/requests/1接收所需的表示/foo
    • 笨重的; 需要来回和一些奇怪的代码.
    • 不可指定和不可缓存,因为/foo/requests/1它只是一个只能使用一次的别名,只保留到请求之前.

Dan*_*zey 12

我会建议查询解决方案(你的第一个).你反对其他选择的论据是很好的论据(也是我在尝试解决同样问题时遇到的问题).特别是,"放松约束/响应foo/a"解决方案可以在有限的情况下工作,但是从实现和消费中向API引入了很多复杂性,并且根据我的经验,并没有值得付出努力.

我会用一个常见的例子来弱对抗你的"似乎意味着"的论点:考虑一个大型对象列表的资源(GET /Customers).分页这些对象是完全合理的,使用查询字符串来做这件事是很常见的:GET /Customers?offset=100&take=50作为一个例子.在这种情况下,查询字符串不会对列出的对象的任何属性进行过滤,而是为对象的子视图提供参数.

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

  • 返回的对象应该与没有查询字符串的Url返回的对象相同.
  • 没有查询字符串的Uri应该返回完整的对象-提供的任何观点的一个超集在相同的URI查询字符串.因此,如果您缓存未修饰的Uri的结果,您就知道您拥有完整的实体.
  • 为给定的查询字符串返回的结果应该是确定性的,因此具有查询字符串的Uris很容易缓存

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

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

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

最终,我认为这可归结为一致性问题:您可以相对轻松地使用查询字符串来满足HATEOAS指导,但您所做的选择需要在您的API中保持一致,并且我会说,有很好的文档记录.


Jor*_*dan 7

我决定以下几点:

支持少数成员组合:我将为每个组合提供一个名称.例如,如果某篇文章中包含作者,日期和正文的成员,/article/some-slug则会返回所有成员,并且/article/some-slug/meta只返回作者和日期.

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

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

建筑约束

休息

识别资源

REST 的定义:

资源R是时间上变化的隶属函数M R(t),其对于时间t映射到一组实体或等价的值.集合中的值可以是资源表示和/或资源标识符.

表示为HTTP主体,标识符为URL.

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

开发人员可以通过考虑"用户"和"帖子"之类的事物来提出合理描述业务的资源定义.

HATEOAS

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

HTTP

从URL 的定义:

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

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

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

功能

任何浏览器或缓存都不会出现斜杠或连字符问题.


Gen*_*nry 5

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

/customers/5

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

响应:

{
   "id": 5,
   "name": "John",
   "surename": "Doe",
   "marital_status": "single",
   "sex": "male",
   ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

/customers/5?fields=name,surename
Run Code Online (Sandbox Code Playgroud)

响应:

{
   "name": "John",
   "surename": "Doe"
}
Run Code Online (Sandbox Code Playgroud)

HTTP / 1.1

  • 如果请求了非法的字段名-返回404(未找到)
  • 如果请求了不同的字段名称-将生成不同的响应,这也与缓存保持一致。
  • 缺点:如果请求相同的字段,但字段之间的顺序不同(例如:fields=id,namefields=name,id),尽管响应相同,但这些响应将分别缓存。

帽子

  • 我认为纯HATEOAS不适合解决此特定问题。因为要实现这一目标,您需要为字段组合的每个排列分配一个单独的资源,这是过大的,因为它使API大量膨胀(例如,资源中有8个字段,您将需要在此处输入图片说明 排列!)。
  • 如果仅对字段而不对所有排列建模,则对性能有影响,例如,您要将往返次数降到最低。