想象一下REST端点(/employees)
以JSON HAL格式提供员工页面。
一名员工住在一个居住在非洲大陆的国家。
对于国家和大陆,也有单独的终点。
返回的页面包含带有员工数据的典型_embedded
字段。
员工资源还包含嵌套的country
资源。
此嵌套country
资源还包含_links
。
在这种情况下,输出将是:
GET /employees
{
"_embedded": {
"employees": [{
"employee_id": 1
"name": "Mr. X",
"place_name": "London",
"country": {
"alpha2_code": "AU",
"name": "Australia",
"continent": {
"code": "OC",
"name": "Australia",
"_links": {
"self": {
"href": "http://localhost:8077/continents/au"
}
}
},
"_links": {
"self": {
"href": "http://localhost:8077/countries/au"
}
}
},
"_links": {
"self": {
"href": "http://localhost:8077/employees/1"
}
}
},
{
..
}
]
},
"_links": {
"first": {
"href": "http://localhost:8077/employees?page=1&size=10"
},
"self": {
"href": "http://localhost:8077/employees"
},
"next": {
"href": "http://localhost:8077/employees?page=2&size=10"
},
"last": {
"href": "http://localhost:8077/employees?page=8&size=10"
}
},
"page": {
"size": 10,
"total_elements": 71,
"total_pages": 8,
"number": 0
}
}
在HAL规范之后,country
(以及continent
的嵌套是否以正确的方式输出country
。
在其他一些例子中,我注意到以下格式:
{
"_embedded": {
"employees": [{
"employee_id": 1
"name": "Mr. X",
"place_name": "London",
"_embedded": {
"country": {
"alpha2_code": "AU",
"name": "Australia",
"_embedded": {
"continent": {
"code": "OC",
"name": "Australia",
"_links": {
"self": {
"href": "http://localhost:8077/continents/au"
}
}
},
}
"_links": {
"self": {
"href": "http://localhost:8077/countries/au"
}
}
}
},
"_links": {
"self": {
"href": "http://localhost:8077/employees/1"
}
}
},
{
..
}
]
},
"_links": {
"first": {
"href": "http://localhost:8077/employees?page=1&size=10"
},
"self": {
"href": "http://localhost:8077/employees"
},
"next": {
"href": "http://localhost:8077/employees?page=2&size=10"
},
"last": {
"href": "http://localhost:8077/employees?page=8&size=10"
}
},
"page": {
"size": 10,
"total_elements": 71,
"total_pages": 8,
"number": 0
}
}
更新:第二个示例现在也清楚地显示它是一个分页响应。
它使用嵌套的_embedded
资源。
是否存在 - 从规范的角度来看 - 一种方法比另一种更好?或两者都有效吗?
答案 0 :(得分:8)
实际上HAL spec非常清楚何时使用_embedded
:
嵌入式资源可以是从目标URI提供的表示的完整,部分或不一致版本。
这有两个含义:
应该在_embedded
下出现的嵌套文档也需要是可链接资源的表示,即它本身需要是一个资源。
放置在_embedded
中的嵌套文档被视为实际资源的预览。除非有嵌套文档的专用资源,否则请不要将其放在_embedded
中。如果您倾向于在嵌套文档中添加self
链接,则需要/应该进入_embedded
。
_embedded
中使用的密钥与同一文档的_links
中显示的链接之间通常存在关联。
将以下代表订单的文件作为示例:
{
"_links" : {
"self" : …,
"customer" : …
},
"items" : [
{
"amount" : …,
"description" : …,
"_links" : {
"product" : …
}
"_embedded" : {
"product" : { … }
}
}
],
"createdDate" : …,
"_embedded" : {
"customer" : {
"firstname" : …,
"lastname" : …
}
}
}
了解items
是如何直接嵌套在文档中的潜在复杂对象的数组。这意味着没有单独的资源代表这些项目。他们是这个资源的一部分。
customer
出现在_links
部分,表示有与此相关的资源,其语义由customer
在应用程序中的含义定义域。 customer
中出现的同一_embedded
基本上表示:此处预览了相关资源的表示形式。嵌套文档可以与您在链接后获得的内容完全相同。但它也可以是完全不同的形状,以满足客户访问当前资源的需求。例如。而不是单独列出firstname
和lastname
,嵌入式变体只能包含displayName
或地址的简单字符串版本,而该地址是实际资源中的复杂对象&#39 ; s代表。
这同样适用于嵌套在订单项表示中的product
。该项目甚至可能持久地从其添加的产品的状态派生description
。但是items.[0]._embedded.product
中列出的内容基本上可以提供有关订单项指向的产品的更深入信息。然而,当然,产品并非包含在内。在订单项中。
这种方法可以使规范中描述的内容为Hypertext Cache Pattern。客户首先检查_embedded.$rel.$interestingProperty
,如果找不到,则转向解析链接并在那里查找$interestingProperty
。这是一个非常标准的实现过程,允许服务器逐步将属性移动到_embedded
,以避免客户端首先需要查找相关资源。 John Moore在this talk中演示了这种方法(使用HTML作为媒体类型,但实际上是相同的模式)。
虽然REST - 甚至更多HAL - 对DDD一无所知,但在设计DDD聚合的表示时,这种区别非常有用,因为它可以区分嵌套的复杂对象。是聚合的一部分(我的示例中的行项)和相关聚合的引用(在我的示例中为客户)。实现后者的主要方法当然是链接,但通常您需要访问相关资源的预览(例如,您要显示其全名的所有订单的主详细信息视图)下订单的客户)。 _embedded
的概念允许您准确表达。
如果您将有效负载返回服务器,那么您还会遇到什么问题。当然,您希望将对资源所做的更改限制为支持它的聚合,而不是跨越多个。在我的示例中,这意味着您自然不希望能够同时更改有关订单的详细信息并更改客户的姓氏,因为该更改将跨越两个聚合,您应该这样做根据DDD避免。通过将客户相关数据移动到mediatype拥有的_embedded
,服务器基本上可以忽略合成字段,只应用对自然字段的更改。