【问题标题】:REST: How to Create a Resource That Depends on Three or More Resources of Different Types?REST:如何创建依赖于三个或更多不同类型资源的资源?
【发布时间】:2014-09-09 21:20:42
【问题描述】:

RESTful,hypertext-driven 系统需要使客户端能够创建依赖于三个或更多不同类型资源的新资源。公开此功能的最佳方法是什么?

例如,假设我经营一家在线商店。服务器知道四种资源:

  • 订单:要发货的产品组。 [有一个货件]
  • 目的地:运送到的位置。 [有很多货件]
  • 发货:将产品发送给客户的行为。 [属于 Destination、Order 和 Packer]
  • Packer:实际准备发货订单的员工。 [有很多货件]

当订单发货时,客户端需要通过在服务器上创建一个新的 Shipment 来记录此事件。货件需要注明目的地、订单和包装商。

要实现新 Shipments 的创建,我可以想到三种方法,但我不喜欢其中任何一种:

  1. 使用 Shipment 媒体类型 POST 到 /shipments。 Shipment 媒体类型具有三个字段:“order_uri”; "packer_uri";和“destination_uri”。每个 URI 分别用作货件中涉及的 Order、Packer 和 Destination 的唯一标识符。
  2. 使用 Shipment 媒体类型 POST 到 /orders/{order_id}/packers/{packer_id}/destinations/{destination_id}/shipments。
  3. 向系统添加一个名为“ShipmentBuilder”的新资源。使用 ShipmentBuilder 媒体类型中包含的“packer_uri”、“destination_uri”和“order_uri”发布到 /shipment_builders。

我不喜欢选项 1,因为 Shipment 媒体类型另外定义了指向 Order、Packer 和 Destination 的链接。这里,“链接”是一个 JSON 哈希,由人类可读的名称、URI 和媒体类型组成。将“order_uri”、“packer_uri”和“destination_uri”添加到媒体类型似乎不是很干,因为它复制了相关资源的 URI。

选项 2 使用深度嵌套的 URI,它们看起来既不易于维护,也不捕获任何有意义的分层信息。

选项 3 在客户端和 Shipments 的创建之间设置了另一个抽象级别,这使得系统更难学习。

如果一个 Shipment 只依赖于一个其他资源,选项 2 会更有意义,但在这种情况下它不是。就目前而言,我更喜欢选项 3,但更喜欢更好的选项。

在此示例中,创建新货件的 URI 和媒体类型的最佳组合是什么?应该考虑哪些其他方法?

更新:以下是 Shipment 资源的 JSON 示例表示,其中显示了订单、包装商和目的地的链接。选项 1 所需的 URI 重复出现在“shipment”哈希中:

{
  "shipment":{
    "created_at": "Wed Sep 09 18:38:31 -0700 2009",
    "order_uri":"http://example.com/orders/815",
    "packer_uri":"http://example.com/packers/42",
    "destination_uri":"http://example.com/destinations/666"
  },
  "order":{
    "name":"the order to which this shipment belongs",
    "uri":"http://example.com/orders/815",
    "media_type":"application/vnd.com.example.store.Order+json"
  },
  "packer":{
    "name":"the person who packed this shipment",
    "uri":"http://example.com/packers/42",
    "media_type":"application/vnd.com.example.store.Packer+json"
  },
  "destination":{
    "name":"the destination of this shipment",
    "uri":"http://example.com/destinations/666",
    "media_type":"application/vnd.com.example.store.Destination+json"
  }
}

“shipment”哈希(减去“created_at”字段)的内容将被 POST。使用 GET 时,将发送上述完整的 Shipment 表示。

【问题讨论】:

  • 您能否详细说明为什么您觉得将 URI 添加到货件媒体类型似乎并不干燥?我不明白你指的是什么重复。
  • Darrel,在底部添加了一些东西。

标签: rest


【解决方案1】:

REST“层次结构”不意味着任何东西。它们便于导航以路径的形式显示关系。不是层次结构本身,而是路径。因此,如果您放弃“层次结构”概念并认识到通往同一最终位置的许多替代路径,那么选项 2 实际上是明智的。

您的选项 2 是订单->打包机->目标路径。从理论上讲,orders->destinations->packers 和 packers->orders->destinations、packers->destinations->orders 以及其他一些都处于同一位置。是的,支持他们所有人是一种痛苦。但是,这证明它们都是等价的并且没有层次结构。

“我不喜欢选项 1,因为 [它] 看起来不太干。”

所以?省略重复的东西。为什么货件还必须包含完整重复的订单和包装商信息? URI 引用足以允许查找和检索 Order 和 Packer。为什么要发送 Order 和 Packer?

“选项 3 使系统更难学习。”为了谁?开发商?您是围绕开发人员设计系统,而不是用户和他们的用例?惭愧。

REST 的要点是(通常)URI 是绝对的、最终的和永恒的东西。哪个替代方案为您提供了绝对最佳的 URI 结构?认识到 URI 是不是层次结构,而是路径——并且对象可以存在于多个替代路径的末尾。

您正在创建一个货件。发布到/shipment。重要的是简单、清晰的 URI。

【讨论】:

  • 您为什么认为“REST 的意义(通常)是 URI 是绝对的、最终的和永恒的东西”?我的理解是,超媒体的原因之一是能够允许服务器在不破坏客户端的情况下更改 URL。这不也是 HTTP 有 301 响应代码的原因吗?当然“酷 URL 不会改变”,但有时我们在定义虚拟路径时会犯错误。
  • 我喜欢您将 URI 描述为路径而不是层次结构。这是一种完全不同的看待事物的方式。但是,当用户单击“主页”链接时,这个比喻并不完全成立。
  • @Daniel Miller:酷 URI 无法更改,否则一切都会中断。 301 是一个真正的错误,一个精心设计的网站不会这样做(除了以浏览器为中心的“redirect-after-POST”hack)。您可以将“家”视为您想要的任何东西;但不要让它混淆你的 URI 的 RESTful 设计。
  • @SLott 没有“URI 的 RESTful 设计”之类的东西。
  • @S.Lott,感谢您的意见。关于选项 1:您可能是对的,并且 Darrel 提供的想法可能是要走的路。重复是由原始设计将 Shipment 属性与 Shipment Links 分开的方式引起的,这反过来似乎是我的框架(Rails)中最简单的方法。可能是时候重新考虑一下了。关于选项 2:听起来我们在说同样的事情——所有 URI 排列都是等价的。我更倾向于选择一个而不是保留所有这些,但选择是任意的。
【解决方案2】:

好的,现在我知道您在哪里看到了重复。发布以下内容是否可行?

{
  "shipment":{
    "created_at": "Wed Sep 09 18:38:31 -0700 2009",
    "order":{
      "uri":"http://example.com/orders/815"
      },
    "packer":{
      "uri":"http://example.com/packers/42",
    }
    "destination":{
      "uri":"http://example.com/destinations/666",
    }
  }
}

并返回这个

{
  "shipment":{
    "created_at": "Wed Sep 09 18:38:31 -0700 2009",
    "order":{
      "name":"the order to which this shipment belongs",
      "uri":"http://example.com/orders/815",
      "media_type":"application/vnd.com.example.store.Order+json"
    },
    "packer":{
      "name":"the person who packed this shipment",
      "uri":"http://example.com/packers/42",
      "media_type":"application/vnd.com.example.store.Packer+json"
    },
    "destination":{
      "name":"the destination of this shipment",
      "uri":"http://example.com/destinations/666",
      "media_type":"application/vnd.com.example.store.Destination+json"
    }
  }
} 

也许这在 JSON 中不起作用,但我在我的资源中对 XML 做了类似的事情。这个想法是,您可以向服务器传递对仅填充 uri 的资源的“引用”,然后服务器填充对象中的其余数据。

【讨论】:

  • 看起来服务器添加的部分是链接“order”、“packer”和“destination”的“name”和“media_type”字段,对吧?如果是这样,此解决方案将删除 URI 引用的重复。在上面,链接包含在“shipment”属性散列中,而在原始媒体类型定义中,链接在属性散列之外。不过,它仍然可以工作。
  • 这种方法引入了一点复杂性,因为客户端需要注意设置“​​order”、“packer”和“destination”链接的“media_type”或“name”属性不允许(创建错误?) - 只能设置每个链接的“uri”属性。
  • 是的,你是对的。我更喜欢 XML 的原因之一是我可以在某些情况下使用属性和元素之间的区别来区分数据和元数据。元数据属性可以帮助客户了解哪些可以更改,哪些不能更改。
【解决方案3】:

我认为 option1 和 option2 是公平的解决方案,我会忘记 option3,因为以前的解决方案是更好的解决方案。

您的客户应始终通过检查链接的语义(例如链接关系和供应商特定的 MIME 类型)而不是通过检查 URL 结构来决定。您不一定需要供应商特定的 MIME 类型,您可以使用 RDF 格式,如 JSON-LD 和 REST 以及应用程序特定的词汇来描述您的链接及其输入字段,您可以使用例如 Hydra。您也可以使用自定义解决方案,例如将 _fields 添加到 _links。

重复链接没有错。如果消息太大,您可以使用 gzip。顺便说一句,您不应该将 URL 与链接混淆,它们是不同的东西。 URL 是资源标识符,链接是对资源的可能操作调用。

【讨论】:

    猜你喜欢
    • 2018-03-01
    • 2023-02-23
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-10
    • 1970-01-01
    相关资源
    最近更新 更多