【问题标题】:How to design REST API for aggregate with nested objects?如何设计用于聚合​​嵌套对象的 REST API?
【发布时间】:2022-01-11 06:00:24
【问题描述】:

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

购物车 1->n 子购物车 1->n CartItem.

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

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

我理解大意,但不清楚参考是什么意思。一些文章谈到内存对象引用,另一些文章也谈到 id 引用。如果用户即想要增加 CartItem 的数量,则 UI 必须有一种方法来识别购物车中的项目。它需要有一些 id/reference。否则购物车怎么知道应该增加什么物品的数量。埃文斯的参考是什么意思?它的措辞非常混乱。

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

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

  1. 修补购物车/{id}/subcarts/{subcart_id}/cartitems/{cartitem_id}

  2. PATCH 购物车/{id}/{subcart_id}/{cartitem_id}

  3. PATCH /carts/{id}?subcart={subcart_id}&cartitem={cartitem_id}

  4. PATCH 购物车/{id} 在正文中包含 subcart_id 和 caritem_id

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

对于选项 3 和 4,返回新的 Cart 对象是很自然的,因为 PATCH 可能导致折扣。

使用选项 1 和 2,在 CartItem 的 PATCH 之后返回 Cart 对象似乎不是 RESTful。返回可能是 204,然后客户端必须再次在购物车上发送 GET,从而导致两次调用。

感谢您的帮助和见解。

【问题讨论】:

    标签: rest reference aggregate domain-driven-design nested-object


    【解决方案1】:

    即使它背后的想法对我来说很清楚,但并不清楚引用的含义。一些文章谈到内存对象引用,另一些文章也谈到 id 引用。如果用户即想要增加项目的数量,则 UI 必须具有对该项目的 id/引用。埃文斯的参考是什么意思?

    指针。

    class A {
       B b
    }
    

    在本例中,A“持有对 B 的引用”。根据 Evan 的指导,如果 A 和 B 都是域实体,那么 B 的此实例与 A 的此实例是同一 AGGREGATE 的成员 B 本身就是其 AGGREGATE 的根实体。


    如何设计用于聚合​​嵌套对象的 REST API?

    REST API 是资源的集合,其中资源可以理解为“网页”的概括。客户端发送消息来操纵资源,有用的业务活动是操纵资源的副作用。见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 向服务器发送域模型命令的表示比尝试从补丁文档中计算命令更容易。请记住,网络使用 HTML 表单和 POST 取得了灾难性的成功。


    从概念上讲,我希望购物车使用单个资源,但是客户端如何发送更新子资源的命令?

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

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

    我们有辅助资源,比如

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

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

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

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


    客户端需要一个完整的购物车来显示所有更改,我想知道是否可以从 CartItem 资源返回一个购物车对象以避免每次两次往返。这会导致我不知道的任何问题吗?

    如果您在响应中包含正确的元数据,那可能会很好。

    PATCH /carts/1/2/3
    
    ...
    
    200 OK
    Content-Location: /carts/1
    
    ...
    

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

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

    【讨论】:

    • 谢谢。从概念上讲,我更喜欢购物车的单个资源,但是客户端如何发送更新子资源的命令?对购物车及其子对象的每次更改都将是来自客户端的单独命令,该命令将立即执行。购物车没有保存选项。因此,如果我使用选项 1 或 2,PATCH 将正常工作。例如,对于 CartItems,我只有 Add、Cancel 和 ChangeQuantity 命令,我可以使用定制的 DTO 处理 HTTP 动词。将所有对象的所有命令映射到单个端点要困难得多。
    • 再次感谢您的详细回复。子资源应该是子对象。 ...以更新 CartItem 的数量为例,正如您还解释的那样,我的原始方法是对 carts/:cartid/:subcartid/:cartitemid 使用 POST,在正文中具有新的数量。处理程序将命令发送到处理它的聚合根。客户端需要一个完整的购物车来显示所有更改,我想知道是否可以从 CartItem 资源返回一个购物车对象以避免每次两次往返。这会导致我不知道的任何问题吗?
    • 只是为了清除注册。聚合:正如你所说,埃文斯并不是指 Id 引用,而只是指针引用。 (这对我来说绝对有意义)。在阅读了同样主张避免外部 id 引用嵌套对象的文章后,我感到很困惑,这让我想知道我是否应该根本不公开嵌套对象的 URI。
    • “这会导致我不知道的任何问题吗?”查看编辑。
    • “这让我想知道我是否应该完全不公开嵌套对象的 URI。”词汇表是一团糟——部分原因是很多人混淆了资源和领域实体。需要注意的是,您在这里引入了耦合——这可能是一种偶然的复杂性,使未来的变化更加昂贵。如果您的域模型仍在发展,那么直接与域实体链接会带来更多风险。这并不意味着它是错误的,而是需要在上下文中进行更深入的分析。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 您好安德里亚,感谢您的回答。我知道超媒体是 REST 的主要部分。除了在返回的 json 中返回指向每个子对象的链接外,我还在研究 HATEOAS 的某些部分。我喜欢保持界面灵活的想法。关于上述选项,我最好说,当从 CartItem 资源返回购物车对象时,选项 1 和 2 不会产生漂亮的界面。我有兴趣了解每个选项的后果。
    猜你喜欢
    • 2015-03-10
    • 1970-01-01
    • 1970-01-01
    • 2020-11-11
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多