【问题标题】:How to handle nested resources with JSON HAL?如何使用 JSON HAL 处理嵌套资源?
【发布时间】:2018-06-05 02:51:33
【问题描述】:

想象一个 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 的嵌套(以及continentcountry 中的嵌套,是否按照 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 资源。

从规范的角度来看,有没有一种方法比另一种更好?还是两者都有效?

【问题讨论】:

    标签: json rest domain-driven-design hal hypermedia


    【解决方案1】:

    实际上HAL spec 非常清楚何时使用_embedded

    嵌入式资源可以是从目标 URI 提供的表示的完整、部分或不一致的版本。

    这有两个含义:

    1. 应该出现在_embedded 下的嵌套文档也需要是可链接资源的表示,即它必须是独立的资源。

      放置在_embedded 中的嵌套文档被视为实际资源的预览。除非嵌套文档有专用资源,否则不要将其放入_embedded。如果您倾向于将self 链接添加到嵌套文档,则需要/应该进入_embedded

    2. _embedded 中使用的键与同一文档的_links 中出现的链接之间通常存在关联。

    一个例子

    以以下表示订单的文档为例:

    {
      "_links" : {
        "self" : …,
        "customer" : …
      },
      "items" : [
        {
          "amount" : …,
          "description" : …,
          "_links" : {
            "product" : …
          }
          "_embedded" : {
            "product" : { … }
          }
        }
      ],
      "createdDate" : …,
      "_embedded" : {
        "customer" : {
          "firstname" : …,
          "lastname" : …
        }
      }
    }
    

    了解items 是如何直接嵌套在文档中的潜在复杂对象数组。这意味着没有单独的资源代表这些项目。它们是该资源的一部分。

    另一方面,customer 出现在_links 部分中,表明存在与此相关的资源,其语义由customer 在应用程序域中的含义定义。同样的customer 也出现在_embedded 中基本上表明:这是相关资源表示形式的预览。如果您点击链接,嵌套文档可能与您获得的完全相同。但它也可以采用完全不同的形式来满足客户访问当前资源的需求。例如。而不是单独列出firstnamelastname,嵌入变体只能包含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 聚合的表示时,这种区别非常有用,因为它允许区分嵌套的复杂对象聚合(我的示例中的行项目)和对相关聚合的引用(我的示例中的客户)。实现后者的主要方法当然是链接,但通常您需要访问相关资源的预览(例如,您希望显示客户全名的所有订单的主详细信息视图下订单)。 _embedded 的概念可以让您准确表达。

    还有一个问题是,如果您将有效负载放回服务器,您实际更新了什么。自然,您希望将对资源所做的更改限制在支持它的聚合中,而不是跨越多个。在我的示例中,这意味着您自然不希望能够同时更改有关订单的详细信息并更改客户的姓氏,因为该更改将跨越两个聚合,根据 DDD,您应该避免这种情况。通过将客户相关数据移动到媒体类型拥有的_embedded,服务器基本上可以忽略合成字段,只应用对自然字段所做的更改。

    【讨论】:

    • 感谢奥利弗非常详细的回答。
    • 看看我原来的问题 - 并考虑到超媒体缓存模式 - 多个嵌套的 _embedded 对象是我认为最接近规范的解决方案,对吗?
    • 是的,我想是的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    • 1970-01-01
    相关资源
    最近更新 更多