同一资源的不同RESTful表示

时间:2013-04-29 07:32:40

标签: api http rest url web-applications

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

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

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

{"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个组件,这可能会成为一场噩梦。
    • 如果我想支持/foo的HTML表示,我必须使用Ajax,如果我只想要一个可以抓取,由极简主义浏览器呈现的HTML页面等,则会出现问题。
    • 要维护HATEOAS,它还需要指向其他表示形式中存在的“子资源”的链接,可能在/foo{"a": {"url": "/foo/a", "content": "some text"}, ...}
  • 请求正文中的
  • GET /fooContent-Type: application/json{"sections": ["a","b","d"]}
    • Unbookmarkable and uncacheable。
    • HTTP不定义GET的正文语义。它是合法的HTTP,但我怎样才能保证某些用户的代理不会从GET请求中删除正文?
    • 我的REST client不允许我在GET请求上放置正文,因此我无法将其用于测试。
  • 自定义HTTP标头:Sections-Needed: a,b,d
    • 如果可能,我宁愿避免自定义标题。
    • Unbookmarkable and uncacheable。
  • 请求正文中的
  • POST /foo/requestsContent-Type: application/json{"sections": ["a","b","d"]}。通过201收到Location: /foo/requests/1。然后GET /foo/requests/1接收所需的/foo代表
    • 笨重;需要来回和一些奇怪的代码。
    • 无法标记且不可缓存,因为/foo/requests/1只是一个只能使用一次的别名,只会在请求之前保留。

5 个答案:

答案 0 :(得分:11)

我建议使用querystring解决方案(你的第一个)。你反对其他选择的论据是很好的论据(也是我在尝试解决同样问题时遇到的问题)。特别是,“放松约束/响应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,则必须返回{{{}的垃圾值1}},或无效的对象)

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

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

答案 1 :(得分:6)

我已决定以下内容:

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

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

无论哪种方式,如果组合不受支持,我都会返回404

架构约束

REST

识别资源

REST的

From the definition

  

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

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

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

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

HATEOAS

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

HTTP

来自网址的definition

  

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

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

HTTP的404代码means,服务器无法找到任何要映射URL的内容,而不是URL未与任何资源相关联。

功能

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

答案 2 :(得分:4)

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

/customers/5

此处'5'代表客户的 id

响应:

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

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

我认为在这种情况下,最可读和最正确的API(例如,只获取名称 surename

/customers/5?fields=name,surename

响应:

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

HTTP / 1.1

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

HATEOAS

  • 在我看来,纯粹的HATEOAS不适合解决这个特殊问题。因为为了实现这一点,你需要一个单独的资源用于 field 组合的每个排列,这是过度的,因为它泛滥了API(假设你在资源中有8个字段,你需要enter image description here排列!)。
  • 如果您只为字段建模资源,而不是为所有排列设置资源,那么它会影响性能,例如:您希望将往返次数降至最低。

答案 3 :(得分:2)

如果a,b,c是像admin这样的资源属性的属性,那么使用正确的方法是你建议GET /foo?sections=a,b,d的第一种方式,因为在这种情况下你应该使用过滤器foo集合。否则,如果a,b和c是foo集合的单一资源,则遵循的方式是执行一系列GET请求/foo/a /foo/b /foo/c。正如您所说,这种方法对请求具有较高的有效负载,但这是遵循Restfull方法的正确方法。我不会使用你提出的第二个提案,因为网址中的加号具有特殊含义。

另一个建议是放弃使用GET和POST,并为foo集合创建一个操作,如:/foo/filter/foo/selection或代表集合操作的任何动词。通过这种方式,拥有一个post请求体,你可以传递你想要的资源的json列表。

答案 4 :(得分:1)

您可以在请求标头应用程序/ vnd.com.mycompany.resource.rep2中使用第二个供应商媒体类型,但是您不能将此书签,但查询参数不可缓存(/ foo?sections = a, b,c)您可以查看矩阵参数但是对于这个问题,它们应该可以缓存URL matrix parameters vs. request parameters