DDD =>根聚合中的行为:instanciate其他根聚合

时间:2018-05-03 16:13:28

标签: domain-driven-design aggregateroot

我有2个根聚合: - 发票 - 投诉

我有一条规则说:"如果在其上打开投诉,我就无法删除发票"

关于我在发票汇总上的删除行为,我想检查是否存在以下投诉:

Complaint complaint = ComplaintRepository.findByInvoiceId(invoiceId);

if(complaint.isOpened) {
throw new Exception("Open Complain...");
}
else{
...
}

我的同事和我对此持不同意见。 他们告诉我,由于投诉不在我的聚合中,我无法在我的行为中实施投诉。 我的意见是我在Invoice Class中没有Complaint属性,但是: - 我可以用一个值对象来引用它(它们对此没问题) - 我可以读取/加载一个实例,因为我没有在其上调用行为......

你对此有何看法?

4 个答案:

答案 0 :(得分:1)

本声明:

  

我有2个根聚合: - 发票 - 投诉`

和这个

  

我有一条规则说:“如果对其进行投诉,我就无法删除发票”

是互斥的,如果你遵循没有大于一个聚合的数据库事务的规则(你应该尝试遵循它,这是一个很好的规则)。

聚合是事务边界,这意味着聚合内部发生的事情与将来在同一聚合中发生的事情非常一致(不变量将保持不管,聚合总是处于有效状态) 。

但是,不同聚合实例之间发生的事情最终是一致的,这意味着没有任何东西可以阻止系统(多个聚合)在没有更高级别协调的情况下进入无效状态。聚合只负责他们拥有的数据。

像你的代码:

Complaint complaint = ComplaintRepository.findByInvoiceId(invoiceId);
//    
// at this time a new complain could be added!!!
//
if(complaint.isOpened) {
    throw new Exception("Open Complain...");
}
else{
   invoiceRepository.delete(invoiceId);// and this would delete the invoice although there is a complain on this invoice!!!
}

将无法遵守业务规则I can't delete an invoice if a complaint is opened on it,除非它包含在一个大于单一的聚合事务中。

话虽如此,你有两个DDD-ish选项:

  1. 检查您的设计:将两个聚合合并为一个,例如,使合规成为发票内的嵌套实体。

  2. 使用更高级别的协调员,将“发票”的“删除”建模为长期运行的业务流程。为此,您可以使用Saga / Process管理器。 “简单”这样的Saga也会删除在删除发票后添加的投诉。更复杂的Saga甚至可以阻止在发票被删除后添加投诉(为此它需要以某种方式拦截投诉的开头)。

答案 1 :(得分:1)

聚合根不应该包含对存储库的引用。这种方法有很多问题。而是从应用程序服务(命令处理程序)中的存储库加载所有对象,并传递给域进行操作。如果操作吞噬了多个聚合,则域逻辑错误(缺少概念)或者您可能需要域服务。无论哪种方式,聚合最好不要向存储库询问任何内容。

答案 2 :(得分:0)

从技术上讲,你可以做你提出的建议:从某个角度来看,如果你通过构造函数注入或方法注入将一个ComplaintRepository接口注入到发票中,那么你将使Invoice依赖于契约无论是存储库还是投诉,这都是允许的。

当你说你不能持有对投诉的引用时,你是对的,但你可以在需要运行时将DDD工件(例如工厂/存储库/实体)注入到操作中。

然而,您必须问自己的要点是:您真的希望在两个不同的聚合之间进行这种级别的耦合吗?在这一点上,他们如此耦合在一起,他们大多不能没有一个和另一个。

考虑到所有这些,您可能会陷入投诉可能只是发票汇总的一部分的情况(尽管您的发票汇总可能还有其他责任,您将开始努力解决“设计小集合”目标) 。如果您考虑一下,那就是不变的“如果在其上打开投诉,我就无法删除发票”,这就是提议。

如果无论如何,将投诉作为发票汇总的一部分进行建模是不切实际的,您还有其他选择:

  • 使这些聚合最终保持一致:不要试图在“一次性”中删除发票,而是在一次操作中将其标记为删除。此操作会在您的消息传递机制中触发某种域事件。然后,此事件“InvoiceFlaggedForDeletion”将检查发票上的投诉。如果您没有投诉,则将其删除。如果您有投诉,则回滚删除标记。

  • 将删除过程放入域服务中。这样,域名服务将协调检查投诉的工作并在适当时删除发票。这种方法的缺点是你的发票实体对它的规则不太明确,但DDD明智这有时是一种可接受的方法。

答案 3 :(得分:0)

另一个考虑应该是 - 删除此域中的发票含义是什么?

请参阅 - http://udidahan.com/2009/09/01/dont-delete-just-dont/

在这种情况下,如果您挑战域专家,可能是围绕“删除”的要求。发票刚刚来自他们接受数据库培训,并隐含地将他们的实际需求转化为解决方案,让您尝试并提供帮助。

或许他们真正在谈论的是取消发票?还是存档?还是扭转它?

在任何情况下,所有这些都允许您在发票上建模状态转换,而不必担心“孤儿”。投诉。

这会促使考虑 - 如果发票被取消,投诉会怎样?是否应通知投诉所有人?投诉应该经历自己的州过渡吗?这可以由InvoiceCancelled事件触发。

在DDD中,每当您看到与删除相关的要求和孤儿记录的问题时,通常都会暗示有一些更深层次的知识需要完成才能理解域的真实意图。