如何为嵌套对象的聚合设计 REST API?

How to design REST API for aggregate with nested objects?

我正在设计我的第一个 REST-API,并且正在努力为具有嵌套对象的 Cart 聚合设计 API:

Cart 1->n SubCart 1->n CartItem.

Cart聚合被整体保存。每次将商品放入购物车时,都必须计算各种折扣,这些折扣取决于其他 CartItem,甚至来自其他 SubCart。因此,客户端应用程序需要接收包含所有嵌套对象的完整购物车聚合。

Evens 在他的 DDD 书中说“...根是 AGGREGATE 中允许外部对象持有对...的引用的唯一成员”。

大致意思我明白了,但不清楚reference是什么意思。一些文章谈到内存对象引用,其他文章也谈到 id 引用。如果用户想增加 CartItem 的数量,UI 必须有办法识别购物车中的商品。它需要有一些 ID/reference。不然Cart怎么知道应该增加什么项目的数量。埃文斯提到参考是什么意思?它的措辞方式非常混乱。

由于所有命令都通过后端的 Cart 聚合,我想知道这是否也应该应用于 REST API。由于我缺乏经验,我不知道一个或另一个解决方案会有什么问题或副作用(即缓存、安全性)?为什么人们会偏爱其中之一?

例如更新 CartItem 的数量,我看到以下选项:

  1. PATCH carts/{id}/subcarts/{subcart_id}/cartitems/{cartitem_id}.
  2. PATCH carts/{id}/{subcart_id}/{cartitem_id}.
  3. PATCH /carts/{id}?subcart={subcart_id}&cartitem={cartitem_id}.
  4. PATCH carts/{id} 体内有 subcart_idcartitem_id

我看到 GIT API 在某些情况下使用了选项 2 的较短形式。什么时候应该选择选项 1 而不是选项 2?

对于选项 3 和 4,由于 PATCH.

,return 新的 Cart 对象可能会有折扣。

对于选项 1 和 2,似乎不是 RESTful 到 return Cart 对象在 CartItemPATCH 之后。 return 可能是 204,然后客户端必须再次在 Cart 上发送 GET,导致两次调用。

感谢您的帮助和见解。

我的建议是尽可能简单易懂。

首先,REST 准则是惯例,而不是一成不变的规则。 还有,你说你怕不是RESTful。我几乎可以肯定你无论如何都不会这样:-)。例如,我几乎可以肯定你根本不会实现 HATEOAS,没有它你就不会创建一个 RESTful 系统(就像你发现的绝大多数所谓的 REST API毕竟:-))

也就是说,您应该考虑您要对其执行的资源和 CRUD 操作。 由于折扣取决于子购物车中是否存在其他商品,我的建议是将购物车视为您的资源,包括其子购物车和商品。这简化了您的工作和对系统的整体理解。

根据性能和清晰度问题做出决定。

Even the idea behind it is clear to me, it is not clear what reference means. Some articles speak about in memory object references, others also speak about id references. If the user i.e. wants to increase the quantity of an item the UI must have an id/reference to that item. What did Evans mean with reference?

指针。

class A {
   B b
}

在此示例中,A“持有对 B 的引用”。根据 Evan 的指导方针,如果 A 和 B 都是域实体,那么 B 的这个实例是与 A 相同的 AGGREGATE 实例的成员 B 本身就是 A 的根实体它的聚合。


How to design REST API for aggregate with nested objects?

RESTAPI是资源的集合,这里的资源可以理解为“网页”的概括。客户端发送消息来操作资源,有用的业务activity是操作资源的副作用。参见 Webber, 2011

换句话说,客户端向 HTTP 服务器发送 PATCH/POST/PUT 消息,然后服务器依次在相应的聚合根实体上调用一些命令。

PATCH /carts/{id}/subcarts/{subcart_id}/cartitems/{cartitem_id}
PATCH /carts/{id}/{subcart_id}/{cartitem_id}
PATCH /carts/{id}?subcart={subcart_id}&cartitem={cartitem_id}
PATCH /carts/{id}

所有这些都很好,因为您可以拥有任意多的不同资源来更改相同的聚合根,而且您可以为资源标识符使用任何您喜欢的拼写约定.这里唯一真正的限制是标识符应该符合 RFC 3986。

注意:在更改后保持所有客户端的不同资源的本地缓存副本同步可能很棘手;因此,如果您不确定自己是否知道自己在做什么,那么我的建议是每个聚合只有一个资源。

It is okay to use POST,您可能会发现使用 POST 将域模型命令的表示形式发送到服务器比尝试从补丁文档计算命令更容易实现。请记住,Web 使用 HTML 表单和 POST.

取得了灾难性的成功

Conseptionally I would favor a single resource for Cart, but how shall the client send a command that updates a sub-resource?

小心 - “子资源”在 REST 上下文中并不是真正的东西。我们有资源,比如

https://datatracker.ietf.org/doc/html/rfc3986

我们还有二手资源,比如

https://datatracker.ietf.org/doc/html/rfc3986#section-3.5

这些都不能很好地映射到聚合根内部的域实体。资源模型是领域模型前面的门面。

在 REST API 中,客户端不直接与域模型交互。相反,客户端将消息寻址到资源,资源与域模型交互(通过聚合根)。

因此,如果您想向隐藏在聚合根“下方”的聚合中某处的域实体显示一些信息,那么您将该信息发送到资源模型中的某个资源,以及资源实现中的消息处理程序与聚合根共享该信息,然后聚合根与聚合内的其他域实体共享该信息。


The client needs a full cart to display all changes and I wondered if it is OK to return a cart object from a CartItem resource to avoid two round-trips each time. Would this cause any issues that I'm not aware of?

如果您在响应中包含正确的元数据,那可能就没问题了。

PATCH /carts/1/2/3

...
200 OK
Content-Location: /carts/1

...

使用以“集合”资源为目标的 POST 请求在服务器上创建新资源并没有什么不同。

就是说,如果您向 /B 发送请求以更改 /A 的表示,那么您就是在违背 HTTP 应用程序设计的潮流,可能需要重新考虑.