【问题标题】:Handling an update a ManyToOne entity collection via REST API通过 REST API 处理更新 ManyToOne 实体集合
【发布时间】:2017-03-11 18:05:52
【问题描述】:

我正在努力思考通过 REST API 将集合更新到另一个资源的最佳方法,并且正在寻找其他人如何看待这个过程的指导。

假设您与实体“父”(一)和“子”(多)之间存在多对一关系。我的想法是,您可以通过单个 PUT 端点处理更新父级的子级集合。这样,用于更新父子实体和将新子实体添加到父集合的端点通过单个端点发生。请求正文将包含一个子实体数组,并且端点本身将包含足够的信息来知道哪个父实体正在被更新:

即PUT .../parent/{uid}/child

端点会告诉我们 uid 为 {uid} 的父实体是被请求的实体,并更新它的子实体。

这种机制感觉有点——奇怪。也就是说,我必须以一种方式持久化新实体,并以另一种方式更新它们。我的更新/保存操作最好是批量执行,但是同时进行批量保存和更新感觉很奇怪。我必须同时做这两件事,因为您无法更新新实体

有没有更好的方法来做到这一点?这种关系是分层的,这意味着没有父资源,子资源就不存在。我仍然希望能够批量 POST/PUT。

我可以公开 POST 与 PUT 的区别(使用与上述相同的端点)。我有一个约束,使得子实体具有唯一的名称,因此 POST 必须失败以使用现有名称发布新的子实体,并且当请求正文包含名称的子实体时,PUT 必须失败不存在。这就是我选择使用单个端点的共享操作的原因。

【问题讨论】:

  • 您能否发布您的父子实体代码?为什么不能简单地执行以下操作:@Transactional public void saveOrUpdate(long parentId, List childs) { Parent parent = parentRepo.findById(parentId); updateParentChilds(parent, childs);} 其中 update parent childs 处理新的、孤儿、更新的孩子...
  • 我没有提供这个,因为它并不能真正帮助解决这个问题,不过感谢您的请求。我不需要帮助来弄清楚如何持久化这些,而是​​更高级别的关于 REST API 和围绕这种父/子关系的设计,其中存在组合类型关系

标签: java spring hibernate rest


【解决方案1】:

处理多对一

关于正确处理多对一关系,让我解释一下如果我要尽可能坚持 ReST 原则,我会怎么做。

嵌套资源

有几种方法可以表达这种关系,一种是您建议使用路径层次结构的方法。您需要以下端点:

  • /parents/
  • /parents/:id
  • /parents/:id/children
  • /parents/:id/children/:id

这种选择更好地表达了孩子们无法独立存在的构图关系。

一级资源

如果您正在应用超媒体约束(您应该将其称为 API ReSTful),则另一个选项如下:

  • /parents
  • /parents/:id
  • /childrens
  • /childrens/id

创建子资源时,您将在请求正文中包含一个指向具有适当 rel 类型的父资源的链接。例如,如果使用HAL

{
  ...
  ...,
  "_links": {
    "parent": { "href": "https://api.domain.com/parents/9283jdp92cn"}
  }
}

这种选择更好地表达了弱关系或聚合,其中关系的两端可以独立存在。

安全上下文

还有第三种选择,我们应该将其视为特殊情况。如果父资源是经过身份验证的主体,您可以隐式地将它们与另一个相关联。例如,如果父级是 User 域实体是 Photos 集合的所有者,您可能会尝试公开以下端点:

  • /users
  • /users/:id
  • /users/:id/photos
  • /users/:id/photos/:id

鉴于只有User 只能访问他自己的Photos,这就足够了:

  • /photos
  • /photos/:id

因为经过身份验证的User 将通过安全上下文可供端点使用,并且可以隐式建立关系,而无需通过分层路径或其他方式显式表达它。

其他注意事项

根据您的问题,我发现了一些可能导致实施不良做法的信号。所以这里有一些与你的帖子相关的原则,你应该尽可能地坚持(务实)。

  1. 每个资源都需要一个唯一标识符,即 ReST 中的 URI。正如您已经发现的那样,在尝试按照问题中的设计更新子实体时,如果不这样做会产生奇怪的影响。
  2. 要成为 ReSTful 的 API 应该实现超媒体约束。如果您的资源 Id 是 URI,则您可以在资源之间创建完全限定且丰富的关系,无论它们的服务器位置如何。
  3. 标识符应该是不透明的。因此,切勿在 URI 的 :id 部分中使用相关数字或枚举。甚至不要让您的消费者尝试猜测可能会暴露您不希望他们看到的内容的 URI。安全是关键,但不透明的 ID 是额外的。
  4. 除非有充分的理由,否则标识符应该由服务器生成,而不是由客户端生成。否则你的 id 不会是不透明的。
  5. 根据经验:

    1. 使用POST 创建并返回201 Created。我喜欢用资源的主体来回应。并且不要忘记包含创建资源的 URI。
    2. 阅读GET并返回200 OK
    3. 使用PUT 修改整个资源。我喜欢与帖子保持一致,并使用200 OK 返回更新后的资源。
    4. 使用DELETE 删除并使用204 No Content 回复。
    5. 我很少使用PATCH 进行部分更新。

这可以满足大多数情况。

【讨论】:

  • 感谢您的意见,您所说的一切对我来说都是有意义的。就我而言,我最好的选择是第一和第三选择的组合(或者您在第三选择的第一部分中标记为想要做的部分)。原因是,我将其开发为独立服务,而 AuthN/AuthZ 引擎位于单独的服务中。我们正在以微服务的形式开发功能。您能否详细说明为什么使用 UID 作为 REST API 的 URI 的一部分会更好?就我而言,用户更容易使用名称,这是唯一字段
  • 我问的原因是其他人问过我同样的问题,我的回答与您在安全方面(猜测 URI)方面的回答相似,但我们有授权拒绝如果用户对用户资源的请求与其身份不匹配,则端点
  • 使 URI 不透明,除了额外的安全性之外,可以确保您的所有 id 都由服务器生成,并且相应的客户端实现是解耦的,至少在这方面是这样。假设这是促进更好客户的最佳实践。它具有与在 API 中使用超媒体类似的属性。如果在客户端正确利用它会进一步解耦客户端实现,但不能确保它。
  • 我接受了你的赏金回答。非常感谢。我接受它的原因是因为您不仅以清晰的方式回答了我的问题,而且还解决了与我的问题相关的许多其他 REST API 组件。既提供幼稚的解决方案,又提供实践中的首选解决方案。
【解决方案2】:

有没有更好的方法来做到这一点?

我认为一定有。需要牢记的一个想法是:统一接口的重点是客户端和中介不需要了解有关服务器上的实现的任何信息。

GET /people/bob/favoriteColors

200 OK
[]

如果这是起点,我们想在列表中添加新颜色

PUT /people/bob/favoriteColors
[ "RED" : { "redChannel":255, "greenChannel":0, "blueChannel":0} ]

200 OK

没问题,我们用 PUT“创建”了一个最喜欢的颜色。

PUT /people/bob/favoriteColors
[ "RED" : { "redChannel":239, "greenChannel":0, "blueChannel":0} ,
[ "BLUE" : { "redChannel":16, "greenChannel":16, "blueChannel":239} ]

200 OK

同样,没问题:我们创建了 BLUE 并更新了 RED。请注意,我们故意与服务器在接受此更新时执行的体操隔离。

KeyValueStore.put(/people/bob/favoriteColors, [...])

KeyValueStore.put(/people/bob/favoriteColors/RED, {...})
KeyValueStore.put(/people/bob/favoriteColors/BLUE, {...})

KeyValueStore.put(/people/bob, {...,favoriteColors:{...}})

RDBMS.commit( [ favoriteColors.insert(BLUE : {}), favoriteColors.update(RED: {})

这并不是说您的 api 不应该允许直接发布到新资源;也不错

PUT /people/bob/favoriteColors/OCTARINE
{ "redChannel":-inf, "greenChannel":Nan, "blueChannel":i}

201 CREATED

您需要记住的是,从您的沼泽标准的角度来看,开箱即用的中间组件,/people/bob/favoriteColors/people/bob/favoriteColors/OCTARINE 之间没有隐含关系。修改一个不会使另一个的缓存条目无效 - 保护我们免受写入实现细节的相同接口也保护我们免受对其他资源的副作用。在设计 API 时,您确实需要考虑拥有可以改变“相同”状态的多个资源的含义。

在某种程度上,你可能还是有这个问题。中介不会知道这一点

DELETE /people/bob

应该驱逐/people/bob/favoriteColors

在到目前为止的所有示例中,我一直在使用所寻址资源的完整表示。这就是 PUT 允许做的所有事情 - 发送目标资源的替换表示。如果您想发送更改的表示,那么您需要考虑 PATCH 或 POST。

POST 与创建的耦合是错误的。我的猜测是假设耦合是为了响应RFC 2616 中对 POST 的描述。 [RFC 7231] 中的语言更多地将其视为包罗万象。 POST 是 HTML 中唯一支持的写入方法,并且网络蓬勃发展,所以我们必须能够以某种方式进行管理。

另一个输出是在所有有趣的工作都是副作用的情况下发送消息。这类似于将消息发送到消息队列。目标资源是队列本身,所以你的请求和响应都对应于将文档添加到集合的概念模型;由于文档本身是要做的工作的表示,而不是结果的表示,它们与领域模型本身是分开的。这意味着您可以将BeigifyColors 命令的完整表示(可以任意复杂)发送到针对特定用例调整的处理程序,并观察表示中的副作用。

这里是命令处理资源,可读表示是 CQRS 模式的另一种表达方式。与 PUT OCTARINE 案例一样,中介不知道要驱逐哪些表示,但除此之外它们处理协议就很好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-05
    • 2018-07-20
    • 2021-08-31
    • 2021-06-11
    相关资源
    最近更新 更多