根据 HAL标准(请参阅here和here),其他资源的链接应放在特定的嵌入式部分中。
所以例如这不是有效的HAL,我的理解是否正确?
{
"movies": [
{
"id": "123",
"title": "Movie title 1",
"_links": {
"subtitles": {
"href": "/movies/123/subtitles"
}
}
},{
"id": "456",
"title": "Movie title 2",
"_links": {
"subtitles": {
"href": "/movies/456/subtitles"
}
}
}
],
"_links": {
"self": {
"href": "/movies"
}
}
}
上述JSON无效HAL的原因是链接应放置在链接到主要ID中的嵌入式部分(" _embedded" )中身体。所以正确的方法是:
{
"movies": [
{
"id": "123",
"title": "Movie title 1",
},{
"id": "456",
"title": "Movie title 2",
}
],
"_embedded": {
"movies": [
{
"id": "123",
"_links": {
"href": "movies/123/subtitles"
}
},
{
"id": "456",
"_links": {
"href": "movies/456/subtitles"
}
}
]
}
"_links": {
"self": {
"href": "/movies"
}
}
}
以上都是正确的吗?
由于
答案 0 :(得分:3)
将这用作关于用hal重新设计的案例研究。 @darrel millers回答很好,但不是很好,有一些我认为应该清理的东西。这将是LONG。
最大的问题是什么是上下文... IE您返回的资源是什么。所有你得到的东西都是某种与电影有关的东西。就像假设这是一个搜索结果一样......拥有电影关系是错误的方法......因为电影与搜索结果相关"顶级资源"作为一个项目。所以它应该更像是
{
title : "Results for search XXXXX"
_links : {
self : { href: "https://host.com/search/with/params/XXXXX"},
item : [
{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"},
{ href : "https://host.com/url/to/movie/result/two", title : "A Terrible Movie"},
]
}
}
但是这个结构对于客户端构建UI来说是昂贵的,因为它必须进行3次调用。遵循N + 1规则(结果集为1,每个结果为N)因此生成_embedded只是hal实现超文本预取模式(在http2中服务器实际上可以发送每个结果作为它自己的文档,客户端的缓存将预先填充这些结果,你不会& #39; t必然需要_embedded)。该结构看起来更像是这样:
{
title : "Results for search XXXXX"
_links : {
self : { href: "https://host.com/search/with/params/XXXXX"},
item : [
{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"},
{ href : "https://host.com/url/to/movie/result/two", title : "A Terrible Movie"},
]
},
_embedded : {
item : [
{
_links : {
profile : {href : "https://host.com/result-movie"},
canonical : {href : "https://host.com/url/to/movie/result/one"}
},
title : "a great movie",
rating : "PG",
},
{
_links : {
profile : {href : "https://host.com/result-movie"},
canonical : {href : "https://host.com/url/to/movie/result/two"}
},
title : "a terrbile movie"
rating : "G",
}
]
}
}
获得3个资源的1个http请求非常棒。 1请求不是N + 1。谢谢你!
那么为什么是项目?搜索结果是否只包含电影......这种情况非常不可能......即使它今天也存在......你是否希望它明天只包含电影...它非常狭窄而这个结构是你必须永远维持的契约。但是您的UI确实希望将结果显示为电影。我已经添加的配置文件链接是...客户端使用配置文件链接来了解它当前正在处理的资源是什么......以及它可以用来构建UI的字段。处理集合时,一个体面的客户端会显示它可以使用的配置文件......并且只是忽略了它可以包含的内容(可能记录警告)。由客户端开发人员升级他们的应用程序以支持新的配置文件...不相信我?想一想网页浏览器如何解析它在html中无法理解的标签......在你的html文档中放置一个<thing-not-invented-yet></think-not-invented-yet>
,看看一个好的客户端是如何工作的。
让我们花点时间谈谈为什么item
作为rel ...因为这非常重要。由于它是搜索结果..为什么rel不是result
。答案非常简单.. result
不是IANA链接注册管理机构https://www.iana.org/assignments/link-relations/link-relations.xhtml的成员,因此result
完全无效......现在您可以&#34;命名空间& #34;您的扩展名为my:result
或our:result
(&#34;名称空间&#34;取决于您,这些只是示例)但如果IANA中已经存在一个非常好的扩展,为什么还要烦恼呢?注册表..它确实item
。
让我们谈谈items
vs item
(或x:movies
vs x:movie
)。好吧items
在IANA也不是。所以它必须是x:items
但是不要这样做,让我们考虑一下原因。如果我们的结果文档用HTML表示,它看起来像这样(忽略我遗漏的身体头部等,为了简洁而不是格式良好):
<html>
<title>Results for search XXXXX</title>
<a rel="item" href="https://host.com/url/to/movie/result/one" >A Great Movie</a>
<a rel="item" href="https://host.com/url/to/movie/result/two" >A Great Movie</a>
</html>
这是第一个示例的SAME资源(没有嵌入子资源)。仅表示为text/html
而不是application/hal+json
。如果我在这里失去了你(这是大多数人真的很困惑的地方,我能提供的最好的就是在https://www.youtube.com/watch?v=u_pZBBELeEQ观看我的谈话)听听它清楚每个人的适当关系目标资源是单个项目而不是一组ITEMS。每个链接都针对一个项目(或一个单一的电影)。
有一个HAL的陷阱可以像JSON那样对待它,这会导致movies
机器可读或更好的注释之类的语句。让我通过在用例中继续使用HTML表示来解释这是如何产生的。
当客户解析此文档以查找item
个链接时,它必须解析每个a
标记,并向下过滤到仅存在rel="item"
属性的标记。这是一个&#34;全表扫描&#34; ..我们如何摆脱这些?我们创建一个索引。 JSON具有内置于其结构中的索引的概念。它是具有数组值的键。 index : [ {entry 1}, {entry 2} ]
。 HAL的作者知道检索链接的最常见方式(在_links或_embedded中预取的链接)将是关系的......所以他构建了他的规范,使得rel被索引。所以当你看到:
_links : {
self : { href: "https://host.com/search/with/params/XXXXX"},
item : [
{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"},
{ href : "https://host.com/url/to/movie/result/two", title : "A Terrible Movie"},
]
},
知道它真的是
_links : {
self : { rel: "self", href: "https://host.com/search/with/params/XXXXX"},
item : [
{ rel:"item", href : "https://host.com/url/to/movie/result/one", title : "A great Movie"},
{ rel:"item", href : "https://host.com/url/to/movie/result/two", title : "A Terrible Movie"},
]
},
因为rel是LINK OBJECT而不是RESOURCE的属性。但是http上的字节是昂贵的(gzip会摆脱这个)并且开发人员不喜欢冗余(整个其他主题)所以当我们有hal时我们OMIT rel属性,因为HAL结构已经使rel显而易见。虽然当你的解析器遇到这个时,它并不是很明显:
{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"}
什么是rel?你必须从父节点传递它...这总是丑陋...反正所有这一切都表明在HAL中通常消除了冗余。一旦消除了这种冗余,就很有可能将该索引键改为复数形式items
,但是知道这意味着你说你的链接(一旦裁员后退了)将是{rel: "items", href : "https://host.com/url/to/movie/result/one", title : "A great Movie"}
并且这显然是错误的..这个链接不是很多项目......只有一个。
因此,在这种情况下删除冗余可能并不是最好的..但它带来好处的恶,HAL遵循_links和_embedded的模式,这就是我们要做的事情。使用我们的搜索结果...发送所有item
链接都没有预先获取并且作为_embedded存在它不重要将它们保存在_links中。因此它应该是这样的:
{
title : "Results for search XXXXX"
_links : {
self : { href: "https://host.com/search/with/params/XXXXX"}
},
_embedded : {
item : [
{
_links : {
profile : {href : "https://host.com/result-movie"},
canonical : {href : "https://host.com/url/to/movie/result/one"}
},
title : "a great movie",
rating : "PG",
},
{
_links : {
profile : {href : "https://host.com/result-movie"},
canonical : {href : "https://host.com/url/to/movie/result/two"}
},
title : "a terrbile movie"
rating : "G",
}
]
}
}
现在我们有一个非常好的搜索结果,其中包括2部电影(未来可以包含更多内容而不违反合同)。注意:如果你曾经使用JUST _links而没有_embedded ...你不能删除_links,因为有些客户端依赖于它们存在...所以最好早点想到这些东西.. 。当使用资源的HAL表示时,一个表现良好的客户端应该始终在_links之前检查_embedded ...所以你真的要知道你的客户是否表现良好。
好的,让我们转到x:movie
是正确关系的情况......如果顶级资源是一个演员,那可能会很好。所以像:
{
Name : "Paul Bettany"
_links : {
canonical : { href: "https://host.com/paul-bettany"},
"x:movie" : [
{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"},
{ href : "https://host.com/url/to/movie/result/two", title : "A Terrible Movie"},
],
"x:spouse" : { href: "", title: "Jennifer Connely"}
},
_embedded : {
"x:movie" : [
{
_links : {
profile : {href : "https://host.com/result-movie"},
canonical : {href : "https://host.com/url/to/movie/result/one"}
},
title : "a great movie",
rating : "PG",
},
{
_links : {
profile : {href : "https://host.com/result-movie"},
canonical : {href : "https://host.com/url/to/movie/result/two"}
},
title : "a terrbile movie"
rating : "G",
}
]
}
}
注意:我在顶层使用了canoncial而不是self,因为actor是长寿资源......演员将永远存在......并且演员没有版本化。为了完整性,我在_links和_embedded中留下了x:movie,但实际上我没有_item中的那些。我还将它们保存在_links中以显示x:movie的原因,以便您可以将它与x:spouse区分开来(在我们开始的搜索结果案例中,语义差异没有意义)。最后,我注意到我嵌入了x:movie
但不是x:spouse
这很有用,这只是为了说明它不是一个或两个东西。您可以预先获取/嵌入您的用例所需的链接。事实上,我经常根据客户的身份嵌入东西。我知道iOS可以显示android无法显示的内容。
除了这些注释之外,我去这里的原因是我想明确表示你没有并且不应该拥有那些电影:你拥有的数据字段......只需依靠_embedded中的电影数据。你说soemthign喜欢将电影中的值与_links或_embedded中的值相匹配......你不应该这样做......这没有任何意义。电影是一种资源......使用电影的链接资源而不是某些数据字段。您需要尽早决定什么是资源以及什么是数据。我最好的建议是,如果一个东西有链接关系......那么它就是一个资源。在我的演讲中,我将通过更广泛的术语(超媒体控件)进一步了解更多细节,我不想进入这里。
最后一点注意事项..在超媒体应用程序中,如果你暴露内部id字段,你知道你做错了什么......就像你在这里做的那样。这应该是一个巨大的红旗,有些事情是错的。您所描述的id的用例是将数据字段电影与_embedded x:movie匹配。如上所述......你不应该这样做......并且id字段的存在应该会让你接受这种糟糕的做法。
我被要求在这里回答。所以我希望这有帮助。
答案 1 :(得分:1)
&#34; _Links&#34;属性必须位于资源对象的根目录中。 资源对象可能位于根目录,也可能位于 _embedded对象中。
我怀疑有些令人困惑的事情来自于#34; _embedded&#34;关键指向一个数组。仅当您要表示相关资源的多个实例时才会执行此操作。
在示例中,密钥为movies
,推断您正在嵌入表示多个电影的资源对象。但是,该数组表示存在多个嵌入式资源对象。每个资源对象都是一部电影。
通过将键名更改为&#34; movie&#34;你明白了:
{
"movies": [
{
"id": "123",
"title": "Movie title 1",
},{
"id": "456",
"title": "Movie title 2",
}
],
"_embedded": {
"movie": [
{
"id": "123",
"_links": {
"href": "movies/123/subtitles"
}
},
{
"id": "456",
"_links": {
"href": "movies/456/subtitles"
}
}
]
}
"_links": {
"self": "https://example.org/movielist"
}
}
所以,现在你有了一个&#34;电影列表&#34;资源对象,你已经嵌入了一堆电影&#34; &#34; movie-list&#34;中每个项目的资源对象。每个资源对象由&#34;电影&#34;有一个&#34; _links&#34;财产相关信息。我假设&#34;字幕&#34;链接应该是一个自我链接。
答案 2 :(得分:0)
在您发布的specs中可以观察到,您可以拥有链接和/或嵌入资源:
资源的链接应该作为该资源的属性:
{
"movies": [
{
"id": "123",
"title": "Movie title 1",
"_links": {
"subtitles": {
"href": "/movies/123/subtitles"
}
}
}, {
"id": "456",
"title": "Movie title 2",
"_links": {
"subtitles": {
"href": "/movies/456/subtitles"
}
}
}
]
}
替代将直接嵌入电影的字幕资源:
{
"movies" : [
{
"id" : "123",
"title" : "Movie title 1",
"_embedded" : {
"subtitles" : [{
"name" : "movie 1 subtitles"
}
]
}
}, {
"id" : "456",
"title" : "Movie title 2",
"_embedded" : {
"subtitles" : [{
"name" : "movie 2 subtitles"
}
]
}
}
]
}