代码之家  ›  专栏  ›  技术社区  ›  M. Koch

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

  •  0
  • M. Koch  · 技术社区  · 4 年前

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

    购物车1->n子部分1->n卡顿项目。

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

    埃文斯在他的DDD书中说“……根是聚合中唯一允许外部对象保存引用的成员……”。

    我理解这个大意,但不清楚引用是什么意思。一些文章讨论内存中的对象引用,另一些文章也讨论id引用。如果用户希望增加购物车项目的数量,用户界面必须能够识别购物车中的项目。它需要有一些id/参考。否则,购物车怎么知道应该增加哪些商品的数量。Evans提到的是什么意思?它的措辞非常令人困惑。

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

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

    1. 修补程序carts/{id}/subcarts/{subcart_id}/cartimes/{cartimes_id}

    2. 修补程序carts/{id}/{subcart_id}/{cartitem_id}

    3. 修补程序/carts/{id}?子部分={subcart_id}&cartitem={cartitem_id}

    4. 修补程序carts/{id}在主体中具有子部件_id和cartitem _id

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

    对于选项3和4,由于补丁的原因,返回新的购物车对象时可能会有折扣,这是很自然的。

    在选项1和2中,在CartItem补丁之后返回Cart对象似乎不是RESTful的。返回的可能是204,然后客户机必须再次发送GET on The Cart,导致两个呼叫。

    谢谢你的帮助和见解。

    0 回复  |  直到 4 年前
        1
  •  2
  •   VoiceOfUnreason    4 年前

    即使它背后的想法对我来说是清楚的,也不清楚引用意味着什么。一些文章讨论内存中的对象引用,另一些文章也讨论id引用。如果用户希望增加某个项目的数量,则UI必须具有该项目的id/引用。Evans提到的是什么意思?

    指针。

    class A {
       B b
    }
    

    在本例中,A“保存对B的引用”。根据Evan的指导原则,如果A和B都是域实体,那么B的这个实例就是A的这个实例的同一个聚合的成员 B本身就是其集合的根实体。


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

    REST API是一组资源,其中的资源可以理解为“web页面”的泛化。客户端发送消息来操纵资源,而有用的业务活动是操纵资源的副作用。看见 Webber, 2011 .

    换句话说,客户机向HTTP服务器发送一个补丁/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取得了灾难性的成功。


    从观念上讲,我倾向于为Cart使用单个资源,但是客户端应该如何发送更新子资源的命令呢?

    小心——“子资源”在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应用程序设计的潮流,可能需要重新考虑。

        2
  •  0
  •   Andrea Chiarelli    4 年前

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

    首先 休息指南是惯例 ,而不是一成不变的规则。 还有,你说你害怕不安静。我几乎可以肯定你无论如何都不会这样:-)。例如,我几乎可以肯定你不会实施 HATEOAS 如果没有它,你就不会创建一个RESTful系统(毕竟,就像你在周围找到的绝大多数所谓RESTAPI一样:-)

    也就是说,你应该 考虑一下你想要对其执行的资源和积垢操作 . 由于折扣取决于其他项目在子车的存在,我的建议是考虑推车作为您的资源,包括其子车和项目。这简化了您的工作和对系统的整体理解。

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