DDD子对象引用

时间:2016-06-08 20:14:57

标签: c# domain-driven-design

我有一个发票对象,它由项目组成,每个项目都与服务有关。

以下结构。

{
   "invoiceId" : "dsr23343",
   "items":{
      "id":1,
      "service":{
         "serviceCode":"HTT"
      }
   }
}

我的一个要求是该项目不应与我们系统中不存在的服务有关。 根据我的理解,域对象永远不应该进入无效状态。 所以我正在做的是:

var service = new Service("SomeService");
var item = new Item(service);
invoice.AddItem(item);

我的问题是,我是否应该要求AddItem函数接收Repository作为第二个参数,如果数据库中不存在Service,则抛出异常?

1 个答案:

答案 0 :(得分:1)

  

我的问题是,我是否应该要求AddItem函数接收Repository作为第二个参数,如果数据库中不存在Service,则抛出异常?

简短的回答:当然,为什么不呢?

更长的答案......

如果Service和Invoice是同一聚合的一部分,则不需要存储库 - 只需查看聚合的状态即可。因此,下面假设发票和服务之间存在交易边界。

使用存储库作为参数有点太多 - 发票不需要加载服务,它只需要知道服务是否存在。因此,不是在方法签名中放置Repository,而是使用支持&#34的DomainService,该服务是否存在?"查询。

(DomainService的实现可能在存储库中进行了查找 - 我们在这里没有做到魔术,我们只是将Invoice与它不需要知道的实现细节隔离开来)

在签名文档中使用限制性更强的界面,明确了这些组件之间的集成合同。

那就是要求非常可疑。如果服务和发票处于不同的聚合中,则它们可能具有不同的生命周期。当您尝试加载发票时应该发生什么,包括引用不再存在的服务的项目?那个用例应该爆炸吗?如果是这样,那么编辑发票就很难解决问题....

如果在将项目添加到发票时,其他一些线程正在删除该服务...?

回顾Udi Dahan的文章:Race Conditions Don't Exist。执行摘要 - 如果您的模型对时间的微秒变化敏感,您可能不会对您的业务进行建模。

你至少有三种其他选择来保护这种"不变的"。

一个是客户端;如果您不让客户生成无效的服务代码,那么您将不会遇到此问题。输入验证属于客户端组件或应用程序组件,而不是模型。也就是说,当应用程序从遍历流程边界的DTO构建ServiceCode时,您可以检查它。

一个是模型的下游 - 如果您可以检测到引用无效服务代码的发票项目,则可以广播异常报告,并使用应急响应流程来管理问题。一致性问题很少见,检测成本低,易于修复,不需要在域模型中进行严格验证。

一个是在模型本身内 - 如果发票项目的创建与服务的生命周期紧密相关,那么该项目可能是由服务创建的,而不是由发票创建的。例如

class Service {
    reportUsage(Customer, TimePeriod)
}

不会是一个不寻常的签名,您可能会相信,提升域事件的服务将正确报告自己的ServiceCode。