Ga *_*chi 1 rest json hateoas discoverability hypermedia
According to the HAL standard (see here and here) the links to other resources should be placed in a specific embedded section.
So for instance this is not valid HAL, is my understanding correct?
{
"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"
}
}
}
Run Code Online (Sandbox Code Playgroud)
The reason for which the above JSON is not valid HAL is that the links should be placed in an embedded section ("_embedded") that links to the ID in the main body. So the right approach would be:
{
"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"
}
}
}
Run Code Online (Sandbox Code Playgroud)
Is all the above correct?
Thanks
将以此作为使用 hal 重新设计的案例研究。@darrel millers 的回答很好,但不是很好,我认为有些事情应该澄清。这会很长。
最大的问题是上下文是什么......即您返回的资源是什么。你所拥有的只是某种与电影有关的东西。就像假设这是一个搜索结果......拥有电影关系是错误的方法......因为电影将搜索结果“顶级资源”作为一个项目。所以它应该更像
{
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"},
]
}
}
Run Code Online (Sandbox Code Playgroud)
但是这种结构对于客户端构建 UI 来说会很昂贵,因为它必须进行 3 次调用……遵循 N+1 规则(结果集 1 次……然后每个结果 N 次)因此诞生了 _embedded只是超文本预取模式的 hal 实现(在 http2 中,服务器实际上可以发送每个结果,因为它是自己的文档,并且客户端的缓存将预先填充这些结果,您不一定需要 _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",
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
获得 3 个资源的 1 个 http 请求非常棒。1 个请求不是 N+1。谢谢哈尔!
那么为什么是项目?好吧,搜索结果是否只包含电影......这不太可能......即使今天有......你是否希望它明天只包含电影......那非常狭窄,这种结构是你的合同基本要永远保持。但是您的 UI 确实希望将结果显示为电影。我添加的配置文件链接是为了...客户端使用配置文件链接来了解它当前正在处理的资源是什么......以及它可以使用哪些字段来构建用户界面。一个体面的客户端在处理集合时会显示它可以显示的配置文件......而忽略它不能的配置文件(可能会记录警告)。由客户端开发人员升级他们的应用程序以支持新的配置文件......不相信我?考虑一下 Web 浏览器如何解析它在 html 中不理解的标签...放一个<thing-not-invented-yet></think-not-invented-yet>在你的 html 文档中,看看一个好的客户端是如何工作的。
你应该注意到的另一件事是我不使用自我链接,而是规范的......多年来我已经改变了我的立场。最近我默认为规范的,并且我只在维护目标资源的版本时才使用 self 并且将嵌入的对象链接到嵌入的特定版本很重要。这在我的经验中非常罕见。我告诉客户跟随它的存在,否则在导航到嵌入的项目时遵循规范。这使服务器可以完全控制它想要将客户端带到哪里。顶级资源,在这种情况下,结果仍然应该有一个自我……在这种情况下,这是有道理的,因为 soem 随机搜索可能没有规范链接……除非它是一个非常常见的关键字搜索……那么它可能应该因为其他用户可以使用相同的网址。
让我们花点时间谈谈为什么item作为相关...因为这真的很重要。既然是搜索结果..为什么不把 rel 设为result. 有一个非常简单的答案......result不是 IANA 链接注册表https://www.iana.org/assignments/link-relations/link-relations.xhtml的成员,因此result完全无效......现在你可以“命名空间“您的扩展名 rel 与my:result或our:result(“命名空间”由您决定,这些只是示例)但是如果 IANA 注册中心中已经存在一个非常好的扩展名,那么为什么还要麻烦呢……而且确实如此item。
让我们谈谈itemsvs item(或x:moviesvs 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>
Run Code Online (Sandbox Code Playgroud)
这是与第一个示例相同的资源(没有嵌入子资源)。只是表示为text/html和 不是application/hal+json。如果我在这里失去了你(这是大多数人真正感到困惑的地方,我能提供的最好的方法是在https://www.youtube.com/watch?v=u_pZBBELeEQ上观看我的演讲)听清楚了每个目标资源的适当关系是单个项目而不是一组项目。每个链接都针对一个项目(或一个单一的电影)。
HAL 存在一个陷阱,将其视为 JSON,这会导致语句类似于movies机器可读或更好的注释。让我通过在用例中继续使用这种 HTML 表示来解释这是如何发生的。当客户端解析此文档以查找item链接时,它必须解析每个a标签并过滤到仅rel="item"存在属性的标签。那是“全表扫描”……我们如何摆脱这些?我们创建一个索引。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"},
]
},
Run Code Online (Sandbox Code Playgroud)
知道这是真的
_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"},
]
},
Run Code Online (Sandbox Code Playgroud)
因为 rel 是链接对象的属性,而不是资源。但是 http 上的字节很昂贵(gzip 会摆脱这一点)并且开发人员不喜欢冗余(整个其他主题)所以当我们有 hal 时,我们省略 rel 属性,因为 HAL 结构已经使 rel 变得明显。尽管当您的解析器遇到以下情况时并不是很明显:
{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"}
Run Code Online (Sandbox Code Playgroud)
什么是相对?你必须从父节点传递它......这总是很难看......无论如何,这一切都是为了表明通常在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",
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们有一个很好的搜索结果,其中包含 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",
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
注意:我在顶层使用了规范而不是自我,因为演员是长期存在的资源..那个演员将永远存在..并且演员没有版本化。为了完整起见,我在 _links 和 _embedded 中都保留了 x:movie,但实际上我不会在 _item 中保留它们。我还将它们保留在 _links 中以显示使用 x:movie is 的原因,以便您可以将其与 x:spouse 区分开来(语义区分在我们开始的搜索结果案例中没有意义)。最后,值得注意的是,我嵌入了x:movie但不是x:spouse这只是为了说明它不是一个非此即彼的东西。您可以预取/嵌入您的用例所需的链接。事实上,我经常根据客户端的身份嵌入一些东西……我知道 iOS 可以显示一些 android 不能显示的东西。
撇开这些注释不谈,我来这里的原因是我想明确表示您没有也不应该拥有电影:您拥有的数据字段......只依赖 _embedded 中的电影数据。您说 soemthign 喜欢将电影中的值与 _links 或 _embedded 中的值相匹配……您不应该这样做……那没有任何意义。电影是一种资源...使用电影的链接资源而不是某些数据字段。您需要尽早决定什么是资源,什么是数据。我最好的提示是,如果一个东西有链接关系……那么它就是一个资源。在我的演讲中,我用更广泛的术语(超媒体控制)更详细地讨论了这个问题,我还不想在这里讨论。
最后一点……在超媒体应用程序中,如果您公开内部 id 字段,您就知道自己做错了……正如您在此处所做的那样。这应该是一个巨大的危险信号,表明出了什么问题。您描述的 id 的用例是将数据字段电影与 _embedded x:movie 相匹配。如上所述......你不应该这样做......并且 id 字段的存在应该让你陷入这种糟糕的做法。
我被要求在这里回答..所以我希望这会有所帮助。
| 归档时间: |
|
| 查看次数: |
1772 次 |
| 最近记录: |