同一资源的不同RESTful表示

我的应用程序在/foo有一个资源。 通常情况下,它是这样的HTTP响应负载:

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

客户端并不总是需要这个对象的全部四个成员。 什么是RESTfully语义的方式让客户告诉服务器在表示中需要什么? 如果它想要:

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

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

  • GET /foo?sections=a,b,d
    • 查询string(毕竟称为查询string)似乎意味着“查找与此条件匹配的资源并告诉我有关它们”,而不是“根据此自定义向我表示此资源”。
  • GET /foo/a+b+d我最喜欢的是REST语义不包含这个问题 ,因为它的简单性。
    • 打破URI不透明,违反HATEOAS。
    • 似乎打破资源(URI的唯一含义是识别一个资源)和表示之间的区别。 但这是值得商榷的,因为它与代表/widget/<id>资源的可视列表/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"]}
    • 没有书签和不可caching。
    • HTTP没有为GET定义正文语义。 这是合法的HTTP,但我怎样才能保证一些用户的代理不剥夺GET请求的身体?
    • 我的REST客户端不会让我把GET请求的身体,所以我不能用它来testing。
  • 一个自定义的HTTP头: Sections-Needed: a,b,d
    • 如果可能的话,我宁愿避免自定义标题。
    • 没有书签和不可caching。
  • POST /foo/requestsContent-Type: application/json{"sections": ["a","b","d"]} 。 用Location: /foo/requests/1接收201 。 然后GET /foo/requests/1接收所需的/foo表示
    • 笨重的; 需要来回和一些奇怪的代码。
    • 由于/foo/requests/1只是一个只能使用一次的别名,并且只有在被请求时才被保留,所以不可用于书签和不可caching。

我会build议查询string解决scheme(你的第一个)。 你反对其他select的论点是很好的论点(当我试图解决同样的问题的时候,我已经遇到了这个论点)。 特别是,“放松对foo/a的约束/响应”解决scheme可以在有限的情况下工作,但是从API实现和消费两方面都给API带来了很多复杂性,而且根本不值得我们付出努力。

我会弱化你的“似乎是”意味着一个常见的例子:考虑资源是一个大的对象列表( GET /Customers )。 页面这些对象是完全合理的,使用查询string来做这件事是司空见惯的: GET /Customers?offset=100&take=50作为一个例子。 在这种情况下,查询string不在列出的对象的任何属性上进行过滤,而是为对象的子视图提供参数。

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

  • 返回的对象应该是与没有查询string的Url返回的实体相同的实体。
  • 没有查询string的Uri应该返回完整的对象 – 在同一个Uri中查询string可用的任何视图的超集。 所以,如果你caching未装饰的Uri的结果,你知道你有完整的实体。
  • 针对给定查询string返回的结果应该是确定性的,这样具有查询string的Uris可以轻松地进行caching

但是,这些Uris 返回什么有时会造成更复杂的问题:

  • 返回一个不同的实体types的Uris不同,只有querystring可能是不受欢迎的( /foo是一个实体,但foo/a是一个string); 另一种方法是返回一个部分填充的实体
  • 如果你使用不同的实体types进行子查询,那么,如果你的/foo没有a ,那么404状态是有误导性的( /foo 确实存在!),但是空的响应可能会同样令人困惑
  • 返回部分填充的实体可能是不受欢迎的,但是返回实体的一部分可能是不可能的,或者可能更令人困惑
  • 如果你有一个强大的模式,返回一个部分填充的实体可能是不可能的(如果a是强制性的,但是客户端只请求b ,则你不得不返回a或一个无效对象的垃圾值)

在过去,我试图通过定义所需实体的具体命名“视图”来解决这个问题,并允许查询string像?view=summary?view=totalsOnly – 限制排列的数量。 这也允许定义对服务的消费者“有意义”的实体的子集,并且可以被logging。

最后,我认为这归结为一致性问题:你可以相对容易地使用查询string来满足HATEOAS的指导,但是你所做的select需要在你的API中保持一致,而且据我所知,这是有据可查的。

我已经决定如下:

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

支持多种组合:我将用连字符分隔成员名称: /foo/abc

无论哪种方式,如果不支持组合,我将返回一个404

build筑约束

rest

识别资源

从 REST 的定义 :

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

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

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

这是由开发人员提出资源定义,通过考虑“用户”和“post”等类别来合理描述业务。

HATEOAS

如果我真的关心完美的HATEOAS,我可以把超链接放在/foo表示的某个地方/foo/members ,而且这个表示只包含一个到每个支持的成员组合的超链接。

HTTP

从URL的定义 :

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

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

HTTP的404代码意味着服务器找不到任何东西来映射URL,而不是URL不与任何资源相关联。

function

浏览器或caching将不会有斜杠或连字符的麻烦。

其实这取决于资源的function。 例如,如果资源代表一个实体:

/customers/5

这里'5'表示客户的ID

响应:

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

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

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

 /customers/5?fields=name,surename 

响应:

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

HTTP / 1.1

  • 如果请求非法字段名称 – 404(未find)返回
  • 如果请求不同的字段名称 – 将生成不同的响应,这也与caching一致。
  • 缺点:如果请求的是相同的字段,但是字段顺序不同(例如: fields=id,namefields=name,id ),虽然响应是相同的,但是这些响应将被分开caching。

HATEOAS

  • 在我看来,纯粹的HATEOAS并不适合解决这个特殊的问题。 因为为了达到这个目的,你需要一个单独的资源来处理字段组合的每一个排列,这是过度的,因为它是广泛地膨胀的API(例如,你有一个资源8个字段,你将需要 在这里输入图像说明 排列!)。
  • 如果仅为字段build模资源,但不是对所有排列进行资源化,则会对性能产生影响,例如,要将往返次数降至最低。

如果a,b,c是资源的属性,如pipe理angular色属性,正确的方法是使用第一种方法,你build议GET /foo?sections=a,b,d因为在这种情况下,你会申请一个过滤到foo集合。 否则,如果a,b和c是foo集合的单数资源,那么将遵循的方法是执行一系列GET请求/foo/a /foo/b /foo/c 。 正如你所说,这种方法对于请求有很高的有效载荷,但是这是遵循Restfull方法的正确方法。 我不会使用你提出的第二个build议,因为URL中的加号具有特殊意义。

另一个build议是放弃使用GET和POST,并为foo集合创build一个动作,如下所示: /foo/filter/foo/selection或表示集合上动作的任何动词。 这样,有一个post请求体,你可以传递一个json的资源列表。

您可以在请求头application / vnd.com.mycompany.resource.rep2中使用第二个供应商介质types,但不能将此标记为书签,但查询参数不可caching(/ foo?sections = a,b,c )你可以看一下matrix参数,但是对于这个问题,他们应该是可caching的URLmatrix参数与请求参数