如何使用JSON HAL处理嵌套资源?

Mar*_*ijk 5 rest json domain-driven-design hal hypermedia

想象一下,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
  }
}
Run Code Online (Sandbox Code Playgroud)

是的嵌套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
  }
}
Run Code Online (Sandbox Code Playgroud)

更新:第二个例子现在也清楚地表明它是一个分页响应.

它使用嵌套_embedded资源.

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

Oli*_*ohm 9

实际上HAL规范很清楚何时使用_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" : …
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

这种方法可以实现规范中描述的超文本缓存模式.客户端_embedded.$rel.$interestingProperty首先进行检查,如果没有找到它,则会解析链接并寻找链接$interestingProperty.这是一个非常标准的实现过程,允许服务器逐步移动属性,_embedded以避免客户端首先需要查找相关资源.John Moore在本次演讲中演示了这种方法(使用HTML作为媒体类型,但实际上是相同的模式).

DDD方面的事情

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

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

  • 看看我原来的问题 - 并考虑超媒体缓存模式 - 多个嵌套的_embedded对象是最接近我认为的规范的解决方案,对吗? (2认同)