1

根据HAL 标准(请参阅此处此处),指向其他资源的链接应放置在特定的嵌入部分中。

因此,例如这不是有效的 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 的原因是链接应放置在嵌入部分(“_embedded”)中,该部分链接到主体中的 ID。所以正确的方法是:

   {
      "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"
          }
       }
    }

以上都是正确的吗?

谢谢

4

3 回答 3

6

将以此作为使用 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"},
      ]

   }  
}

但是这种结构对于客户端构建 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",
       }
     ]
   }

}

获得 3 个资源的 1 个 http 请求非常棒。1 个请求不是 N+1。谢谢哈尔!

那么为什么要物品呢?那么搜索结果是否只包含电影......这不太可能......即使它今天......你是否希望它只包含明天的电影......这是非常狭窄的,这个结构是你的合同基本上要一直维持。但是您的 UI 确实希望将结果显示为电影。我添加的配置文件链接是为了......客户端使用配置文件链接来了解它当前正在处理的资源是什么......以及它可以使用哪些字段来构建 UI。一个体面的客户端在处理一个集合时会显示它可以显示哪些配置文件……而忽略它不能处理的配置文件(可能会记录警告)。由客户开发人员升级他们的应用程序以支持新的配置文件......不相信我吗?考虑一下 Web 浏览器如何解析它在 html 中无法理解的标签...放一个<thing-not-invented-yet></think-not-invented-yet>在你的 html 文档中,看看一个好的客户端是如何工作的。

您应该注意的另一件事是我不使用自我链接,而是使用规范的..多年来我已经改变了我的立场。最近我默认为规范,并且我只在维护目标资源的版本时使用 self ,并且将嵌入的对象链接到嵌入的特定版本很重要。这在我的经验中非常罕见。我告诉客户跟随它的存在,否则在导航到嵌入项目时遵循规范。这使服务器可以完全控制它要将客户端带到哪里。顶级资源,在这种情况下,结果应该仍然有一个自我......在这种情况下,它是有意义的,因为 soem 随机搜索可能没有规范链接......除非它是一个非常常见的关键字搜索......然后它可能应该因为其他用户可以使用相同的 url。

让我们花点时间谈谈为什么item作为 rel...因为这真的很重要。既然是搜索结果..为什么没有 rel be result。有一个非常简单的答案..result不是 IANA 链接注册表https://www.iana.org/assignments/link-relations/link-relations.xhtml的成员,因此result完全无效......现在你可以“命名空间“您的扩展名 rel 与my:resultor 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>

这是与第一个示例相同的资源(不嵌入子资源)。只是表示为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"},
      ]

   },

知道这是真的

   _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 而不是资源的属性。但是 http 上的字节很昂贵(gzip 会摆脱这一点)并且开发人员不喜欢冗余(完全是另一个主题)所以当我们有 hal 时我们省略了 rel 属性,因为 HAL 结构已经使 rel 明显。尽管当您的解析器遇到以下情况时并不明显:

{ href : "https://host.com/url/to/movie/result/one", title : "A great Movie"}

什么是相对?你必须从父节点传递它......这总是很丑陋......无论如何,这一切都是为了表明 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 因为演员是长期存在的资源..演员将永远存在..演员没有版本。为了完整起见,我将 x:movie 都留在了 _links 和 _embedded 中,但实际上我不会在 _item 中使用它们。我还将它们保存在 _links 中以说明使用 x:movie is 的原因,以便您可以将其与 x:spouse 区分开来(在我们开始的搜索结果案例中,语义区分没有意义)。最后,需要注意的是我嵌入了x:movie但不是x:spouse这只是为了说明它不是一个非此即彼的东西。您可以预取/嵌入用例所需的链接。事实上,我经常根据客户端的身份嵌入东西……我知道 iOS 可以显示 android 不能显示的东西。

撇开那些笔记不谈,我去这里的原因是我想明确表示你没有也不应该拥有那些电影:你拥有的数据字段......只需依赖 _embedded 中的电影数据。你说 soemthign 就像将电影中的值与 _links 或 _embedded 中的值匹配......你不应该那样做......这没有任何意义。电影是一种资源...使用电影的链接资源而不是某些数据字段。您需要尽早决定什么是资源,什么是数据。我最好的建议是,如果一个事物具有链接关系......那么它就是一种资源。在我的演讲中,我用更广泛的术语(超媒体控制)对此进行了详细介绍,我还不想在这里讨论。

最后一点..在超媒体应用程序中,如果您公开内部 id 字段,您知道您做错了..就像您在此处所做的那样。这应该是一个巨大的危险信号,表明有问题。您描述的 id 的用例是将数据字段 movies 与 _embedded x:movie 匹配。如前所述...您不应该这样做...并且 id 字段的存在应该使您陷入这种不良做法。

我被要求在这里回答..所以我希望这会有所帮助。

于 2017-06-24T17:45:22.517 回答
1

“_Links”属性必须位于资源对象的根目录中。该资源对象可能位于根目录,也可能位于_embedded object中。

我怀疑一些令人困惑的原因是有一个“_embedded”关键点指向一个数组。仅当您要表示相关资源的多个实例时才这样做。

在示例中,关键在于movies推断您正在嵌入代表多部电影的资源对象。但是,该数组表示有多个嵌入的资源对象。每个资源对象是一部电影。

通过将键的名称更改为“电影”,您将获得以下信息:

   {
      "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"
       }
    }

所以,现在你有了一个“电影列表”资源对象的表示,并且为“电影列表”中的每个项目嵌入了一堆“电影”资源对象。“movie”指向的每个资源对象都有一个指向相关信息的“_links”属性。我假设“字幕”链接应该是自链接。

于 2017-06-23T21:35:50.340 回答
0

正如您发布的规范中所观察到的那样,您可以拥有链接和/或嵌入式资源:

在此处输入图像描述

资源的链接应该作为该资源的属性:

{
  "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"
                }
            ]
          }
        }
    ]
}

在此处输入图像描述

于 2017-06-23T07:35:06.273 回答