一尘不染

HAL-如果链接位于主体中,是否违反HAL格式/标准?

json

根据 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无效的原因是,链接应放在链接到主体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"
          }
       }
    }

以上所有正确吗?

谢谢


阅读 234

收藏
2020-07-27

共1个答案

一尘不染

将用它作为用hal重新设计的案例研究。@darrel millers的回答是好的,但不是很好,我认为有些事情应该清除。这将是漫长的。

The big question is what is the context…IE what is this resource you are
returning. All you’ve got is something that relates to movies somehow. Like
suppose this is a search result…having a movie relationship is the wrong
approach..as the movie relates to the search result “top level resource” as an
item. so it should be something more like

{
   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只是超文本预取模式的半个实现(在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",
       }
     ]
   }

}

1个HTTP请求获得3个资源真是太好了。1个请求不是N + 1。谢谢哈尔!

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

您应该注意的另一件事是,我不使用自我链接而是规范的。多年来,我改变了对此的立场。到目前为止,我默认使用规范,并且仅在维护目标资源的版本时才使用self,将嵌入式对象链接到所嵌入的特定版本非常重要。根据我的经验,这非常罕见。我告诉客户跟随它的存在,或者在导航到嵌入项时遵循规范。这样,服务器就可以完全控制将客户端移至何处。顶级资源,在这种情况下,结果应该仍然具有自我…在这种情况下,这是有意义的,因为随机搜索可能没有规范的链接…除非它是非常普通的关键字搜索…然后它可能应该与其他用户可以使用相同的网址。

让我们花点时间讨论一下为什么item会如此,因为这真的很重要。由于这是一个搜索结果。.为什么没有rel是result。有一个非常简单的答案..
result不是IANA链接注册表的成员https://www.iana.org/assignments/link-relations/link-
relations.xhtml,因此result完全无效…现在您可以“命名空间”您的扩展名依赖于my:resultour:result(“命名空间”由您决定,这些仅是示例),但是,如果IANA注册中心中已经存在一个非常好的扩展名,那么为什么还要为此烦恼item

让我们谈谈itemsvs item(或x:moviesvs
x:movie)。好吧items,IANA也没有。.so一定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。如果我在这里迷路了(这是大多数人真正感到困惑的地方,那么我能提供的最好的办法就是在

HAL有一个陷阱,将其像JSON一样对待,并导致产生类似于movies机器可读或更好的注释的语句。让我通过在用例中继续此HTML表示来解释这是如何实现的。当客户解析此文档以查找item链接时,它必须解析EVERY
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是链接对象而不是资源的属性。但是http上的字节很昂贵(gzip会摆脱它),并且开发人员不喜欢冗余(整个主题),所以当我们暂停时,由于HAL结构已经使rel变得明显,所以我们忽略了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部电影(并且将来可以包含更多内容而不会违反合同)。注意:如果您以前只是使用_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是长期存在的资源。该actor将一直存在。而且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字段的出现应该使您陷入这种不良做法。

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

2020-07-27