【问题标题】:DDD update via REST通过 REST 更新 DDD
【发布时间】:2016-06-17 21:18:25
【问题描述】:

我是 DDD 的新手,我正在尝试找出一种使用 PUT 动词更新聚合的方法。

如果聚合中的所有属性都有私有设置器,那么很明显我需要为每个业务需求提供一组功能。举个例子

supportTicket.Resolve(); 

很明显,我可以使用/api/tickets/5/resolve 等端点来实现这一点,但是如果我想提供一种自动更新整个票证的方法呢?

例如,用户可以使用以下正文向/api/tickets/5 发出 PUT 请求

{"status" : "RESOLVED", "Title":"Some crazy title"}

我需要在 ApplicationService 中做这样的事情吗

if(DTO.Status != null && dto.Status == "RESOLVED")
  supportTicket.Resolve();
if(DTO.Title != null)
  supportTicket.setNewTitle(DTO.title);

如果是这种情况,并且更改工单标题有一些业务逻辑可以防止在工单得到解决时更改它,我应该在更新聚合时考虑某种优先级,还是我认为这完全错误?

【问题讨论】:

标签: c# asp.net-web-api2 domain-driven-design


【解决方案1】:

Domain Driven Design for RESTful Systems -- 吉姆·韦伯

如果我想提供一种自动更新整个票证的方法怎么办?

如果你想以原子方式更新整个票证,请放弃聚合;如果您真正想要的是具有 CRUD 语义的键值存储,那么聚合是错误的工具。

聚合仅在它们是域要强制执行的业务规则时才有意义。当您只需要一把铲子时,不要制造拖拉机。

例如,用户可以向 /api/tickets/5 发出 PUT 请求

那会弄得一团糟。在 CRUD 实现中,通过向资源发送新状态的表示来替换资源的当前状态是合适的。但这根本不适合聚合,因为聚合的状态不受您(客户/发布者)的控制。

更合适的习惯用法是将消息发布到总线上,当由域处理时,将产生实现您想要的更改的副作用。

PUT /api/tickets/5/messages/{messageId}

现在您的应用程序服务查看消息,并向聚合发送命令

if(DTO.Status != null && dto.Status == "RESOLVED")
  supportTicket.Resolve();
if(DTO.Title != null)
  supportTicket.setNewTitle(DTO.title);

这没问题,但实际上更常见的是让消息明确说明要做什么。

{ "messageType" : "ResolveWithNewTitle"
, "status" : "RESOLVED"
, "Title":"Some crazy title"
}

甚至……

[
  { "messageType" : "ChangeTitle"
  , "Title" : "Some crazy title"
  } 
, { "messageType" : "ResolveTicket"
  }
]

基本上,您希望为应用提供足够的上下文,使其可以进行真正的消息验证。

假设我有封装了所需业务逻辑的聚合,但除此之外,还有对原子更新功能的新需求,我正在尝试了解解决此问题的最佳方法。

因此,解决此问题的正确方法是首先在领域级别进行处理——与您的领域专家坐下来,确保每个人都理解需求以及如何用通用语言表达它等。

在聚合根中实现您需要的任何新方法。

一旦您在域中正确支持了用例,那么您就可以开始按照之前的模式来担心您的资源 - 资源只是接受传入的请求,并调用适当的命令。

【讨论】:

  • 我知道聚合不是理想的解决方案,但是假设我有封装了所需业务逻辑的聚合,但除此之外,还有对原子更新功能的新需求,我正在尝试了解最佳方法处理这个问题。
  • @Robert 你不应该在同一个聚合上混合使用 CRUD 操作和行为操作。无论如何,我看不出这在哪里有用。
  • 我可以在获取资源时实现超媒体,并描述可以对该资源执行的所有可能操作,因此 api 使用者可以看到我的业务逻辑并确切知道可以更改什么以及如何做是吗?
【解决方案2】:

更改标题是否是解决票证的要求?如果不是,它们不应该是 DDD 中的相同操作。如果新名称无效,您不希望不解析票证,如果票证不可解析,您也不希望不更改名称。

进行 2 次调用以执行 2 个单独的操作。这也允许灵活性,例如可以立即更改标题,但也许“解决”票证将在票证实际解决之前启动一些复杂且耗时(异步)的工作流程。也许它需要一个经理签字?您不希望更改“标题”的调用与该组合捆绑在一起。

如果需要,根据@VoiceOfUnreason 的评论创建一些东西来编排多个命令。

尽可能将事物分开,并根据用例编写代码,而不是尽量减少与实体的交互。

【讨论】:

    【解决方案3】:

    你可能是对的。但是,通过创建“change()”方法、接收 changeCommandModel(或类似的东西)将此类逻辑封装在它自己的票证中可能更明智,这样您就可以在域对象中定义业务规则。

    【讨论】:

      【解决方案4】:
        if (DTO.Status != null && dto.Status == "RESOLVED")
                        supportTicket.Resolve(DTO.title);
      

      我将更改底层方法以将标题作为参数,这阐明了解析操作。第二个 if 和验证你想要在域方法中。这真的是偏好,更重要的是信息,我同意@VoiceOfUnreason 的第二个选项。

      【讨论】:

      • 如果这是唯一可能的情况,这会很好,但如果用户在正文中发送更多参数,这将不会坚持。
      猜你喜欢
      • 2018-03-20
      • 1970-01-01
      • 2021-06-11
      • 2017-12-26
      • 2019-07-13
      • 2020-03-30
      • 2017-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多