想象一个 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
}
}
是嵌套的
country
(还有 continent
在 country
中的嵌套,按照 HAL 规范以正确的方式输出。在其他一些示例中,我注意到以下格式:
{
"_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
资源。从规范的角度来看,是否有一种方法比另一种更好?还是两者都有效?
最佳答案
其实HAL spec很清楚何时使用 _embedded
:
Embedded Resources MAY be a full, partial, or inconsistent version of the representation served from the target URI.
这有两个含义:
_embedded
下的嵌套文档还需要是可链接资源的表示,即它本身需要是一个资源。嵌套文档放置在
_embedded
被视为实际资源的预览。除非有嵌套文档的专用资源,否则不要将其放入 _embedded
.如果您倾向于添加 self
链接到嵌套文档,它需要/应该进入 _embedded
._embedded
中使用的 key 之间通常存在连接。以及出现在 _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
,或地址的简单字符串版本,该地址是实际资源表示中的复杂对象。这同样适用于
product
嵌套在行项目表示中。该项目甚至可能有 description
持久地源自添加它的产品的状态。但是items.[0]._embedded.product
中列出的内容基本上可以携带有关订单项指向的产品的更深入的信息。但是,当然,该产品并未“包含”在行项目中。这种方法支持规范中描述为 Hypertext Cache Pattern 的内容。 .客户考察
_embedded.$rel.$interestingProperty
首先 - 如果它没有找到它 - 求助于解析链接并寻找 $interestingProperty
那里。这是一个非常标准的实现过程,并允许服务器逐渐将属性移动到 _embedded
避免客户端首先需要查找相关资源。 John Moore 在 this talk 中演示了这种方法(使用 HTML 作为媒体类型,但实际上是相同的模式)。事情的 DDD 方面
尽管 REST —— 更何况 HAL —— 对 DDD 一无所知,但在设计 DDD 聚合的表示时,这种区别非常有用,因为它允许区分作为聚合一部分的嵌套复杂对象(示例中的行项目)和对相关聚合的引用(示例中的客户)。实现后者的主要方法当然是链接,但通常您需要访问相关资源的预览(例如,您希望显示客户全名的所有订单的主详细信息 View 下订单)。
_embedded
的概念允许您准确地表达这一点。还有一个问题是,如果您将有效负载放回服务器,您实际更新的内容是什么。自然地,您希望将对资源所做的更改限制在支持它的聚合中,而不是跨越多个聚合。在我的示例中,这意味着您自然不希望能够同时更改有关订单的详细信息并更改客户的姓氏,因为该更改将跨越两个聚合,根据 DDD,您应该避免这种情况。通过将客户相关数据移动到媒体类型拥有的
_embedded
,服务器基本上可以忽略合成字段,只应用对自然字段所做的更改。
关于json - 如何使用 JSON HAL 处理嵌套资源?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47941703/