如何使用 JSON HAL 处理嵌套资源? [英] How to handle nested resources with JSON HAL?

查看:78
本文介绍了如何使用 JSON HAL 处理嵌套资源?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

想象一个 REST 端点 (/employees) 以 JSON HAL 格式为员工页面提供服务.一名员工居住在一个国家,该国家位于一个大陆.

Imagine a REST endpoint (/employees) serving pages of employees in JSON HAL format. An employee lives in a country, which resides in a continent.

对于国家和大洲,也有单独的端点.

For both countries and continents there are also separate endpoints.

返回的页面包含典型的 _embedded 字段和员工数据.员工资源还包含嵌套的 country 资源.这个嵌套的 country 资源还包含 _links.

The returned pages contain the typical _embedded field with the employee data. The employee resource also contains the nested country resource. This nested country resource also contains the _links.

在这种情况下,输出将是:

In this case the output would be:

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的嵌套(也是countrycontinent的嵌套,按照HAL规范以正确的方式输出.

Is the nesting of the country (and also the nesting of continent within the country outputted in the correct way following the HAL specification.

在其他一些示例中,我注意到以下格式:

In some other examples on the I noticed the following format:

{
  "_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 资源.

It uses nested _embedded resources.

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

Is there - in perspective of the specification - one approach better then the other? Or are the both valid?

推荐答案

其实就是 HAL 规范 非常清楚何时使用 _embedded:

Actually the HAL spec is pretty clear about when to use _embedded:

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

Embedded Resources MAY be a full, partial, or inconsistent version of the representation served from the target URI.

这有两个含义:

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

  1. The nested document that's supposed to appear under _embedded also needs to be a representation of a linkable resource, i.e. it needs to be a resource on its own.

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

The nested document placed in _embedded is considered a preview of the actual resource. Unless there's a dedicated resource for the nested document, don't put it in _embedded. If you're inclined to add a self link to the nested document, it needs to / should go into _embedded.

_embedded 中使用的键和出现在同一文档的 _links 中的链接之间通常存在联系.

There's usually a connection between the keys used within _embedded and a link that appears in _links of the same document.

一个例子

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

An example

Take the following document representing an order as example:

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

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

See how items is an array of potentially complex object directly nested in the document. That means there's no separate resource representing the items. They're part of this resource.

customer 另一方面出现在 _links 部分,表明有一个与此相关的资源,其语义由 customer 表示在应用程序域中.同样出现在 _embedded 中的 customer 基本上表明:这是相关资源表示形式的预览.嵌套文档可能与您按照链接获得的内容完全相同.但它也可以是完全不同的形状来满足访问当前资源的客户端的需求.例如.而不是分别列出 firstnamelastname,嵌入的变体只能包含一个 displayName,或者是一个复杂对象的地址的简单字符串版本在实际资源的表示中.

customer on the other hand appears both in the _links section, indicating there's a resource related to this one, whose semantics are defined by what customer means in the application domain. That same customer also appearing in _embedded basically indicates: here's a preview of what the representation of the related resource looks like. The nested document can be completely identical to what you'd get if you followed the link. but it can also be of completely different shape to serve the clients needs accessing the current resource. E.g. instead of listing firstname and lastname separately, the embedded variant could only contain a displayName, or a simple string version of an address that's a complex object in the actual resource's representation.

这同样适用于嵌套在行项目表示中的 product.该项目甚至可能具有从其添加的产品状态持续派生的 description.但是 items.[0]._embedded.product 中列出的内容本质上可以包含有关该行项目指向的产品的更深入的信息.然而,当然,产品不是包含"的.在订单项中.

The same applies to the product nested inside the line item representation. The item might even have the description persistently derived from the state of the product it was added. But what's listed in items.[0]._embedded.product could essentially carry more in-depth information about the product the line item is pointing to. However, of course, the product is not "contained" in the line item.

这种方法支持规范中描述的超文本缓存模式.客户端首先检查 _embedded.$rel.$interestingProperty 并且 - 如果它没有找到它 - 求助于解析链接并在那里寻找 $interestingProperty .这是一个非常标准的实现过程,允许服务器逐渐将属性移动到 _embedded 中,以避免客户端首先需要查找相关资源.John Moore 在 实际上是相同的模式).

This approach enables what's described in the spec as Hypertext Cache Pattern. The client inspecting _embedded.$rel.$interestingProperty first and -- in case it's not finding it -- resorts to resolving the link and looking for $interestingProperty there. That's a pretty standard procedure to implement and allows a server to gradually move properties into _embedded to avoid the clients to need to lookup the related resource in the first place. John Moore demonstrates this approach in this talk (using HTML as media type but effectively the same pattern).

尽管 REST —— 更何况 HAL —— 对 DDD 一无所知,但在设计 DDD 聚合的表示时,这种区别非常有用,因为它允许区分嵌套的、复杂的对象是一部分聚合(我的示例中的行项目)和对相关聚合(我的示例中的客户)的引用.实现后者的主要方法当然是链接,但通常您需要访问相关资源的预览(例如,您希望显示客户全名的所有订单的主详细信息视图下订单)._embedded 的概念可以让您准确表达这一点.

Although REST -- and even more so HAL -- doesn't know anything about DDD, this distinction is pretty helpful when it comes to designing representations of DDD aggregates, as it allows to differentiate between nested, complex objects that are part of the aggregate (the line items in my example) and references to related aggregates (the customer in my example). The primary means to implement the latter are links of course, but very often you need to have access to a preview of the related resource (e.g. a master detail view on all orders for which you'd like to display the full name of the customer that placed the order). The concept of _embedded allows you to express exactly that.

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

What also plays into this is the question of what you actually update if you PUT a payload back to the server. Naturally you want to limit changes made to the resource to the aggregate that's backing it and not span multiple ones. In my example that means you naturally wouldn't want to be able to at the same time change details about the order and change the customer's lastname as that change would span both aggregates, which you're supposed to avoid according to DDD. By moving the customer related data into the mediatype-owned _embedded, the server can basically ignore the synthetic fields and only apply changes made to the natural ones.

这篇关于如何使用 JSON HAL 处理嵌套资源?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆