DDD域服务

时间:2016-07-12 21:18:16

标签: domain-driven-design ddd-service

我有一个Invoice聚合根,在某些时候可以发送到会计外部Web服务,并通过持久保存从该服务获得的一些ID /号码来标记为发送。

在DDD中执行此操作的正确方法是什么?

以下是我的想法:

首先:

拥有带功能SendToAccounting的发票AggregateRoot,并注入域服务/接口,它将发票发送到会计,并检索一些" id / code"在会计软件中,并设置AccountingSoftwareId属性

Invoice.SendToAccounting(IInvoiceDomain service)
{
     var accountingSoftwareID = service.getAccountingSoftwareId(this);
     this.AccountingSoftwareId = accountingSoftwareId;
}

///Implementation in the application service
    var invoice = _invoiceRepository.GetInvoiceById(id);
    invoice.SendToAccounting(someDomainService);
    _invoiceRepository.Update(invoice);
    _unitOfWork.Save();

第二种方法:

与第一种方法类似,但域名服务应负责持久保存:

var invoice = _invoiceRepository.GetInvoiceById(id);
///unit of work save will be called inside this function
invoice.SendToAccounting(someDomainService);

第三个方法:

域服务将完全重新负责封装此行为

///Code inside domain service
public void SendInvoiceToAccounting(int invoiceId)
{
    var invoice =  _invoiceRepository.GetInvoiceById(invoiceId);
    string invoiceAccountingId = _accountingService.GetAccountingSoftwareId(invoice);
    invoice.SetAsSentToAccounting(invoiceAccountingId);
    _invoiceRepository.Update(invoice);
    _unitOfWork.Save();
}

3 个答案:

答案 0 :(得分:1)

  

在DDD中执行此操作的正确方法是什么?

你的第一种方法是最接近的。域服务上的签名应该接受状态作为参数,而不是聚合根本身。

Invoice.SendToAccounting(IInvoiceDomain service)
{
    var accountingSoftwareID = service.getAccountingSoftwareId(this.Id, ...);
    this.AccountingSoftwareId = accountingSoftwareId;
}

传递的所有参数都应该是值类型 - 域服务不应该通过操纵其参数副本来更改聚合的状态,并且它当然不应该能够在其上运行其他命令聚合

在代码审查中,我会拒绝您提供的第二种方法;从域模型的角度来看,域服务接口应该只提供查询,而不是命令(在CQS意义上)。

在代码审查中,我会完全拒绝第三种方法 - 聚合的设置者是代码气味;重点是用更新规则封装状态。

BUT

设计有点令人担忧,因为您在同一事务中的两个不同位置进行写入。在快乐的道路上,这不是什么大问题,但是如果在会计服务上运行的命令成功,你应该做什么,但更新发票的保存失败了?

假设分布式交易没有吸引力,您可能想要查看Udi Dahan对reliable messaging的评论。

答案 1 :(得分:0)

我的第一个想法是“不是会计部分的发票?” :)

选项1是我过去倾向于使用的域名对象具有行为的内容。

我不喜欢选项2,因为Invoice需要对存储库的私有引用。

更普遍的观察是,这里的域中似乎没有太多行为 - 它似乎只是设置了一个id。选项3似乎捕获了这一点。我想知道一个应用程序服务是否足够,只是协调以下

  1. 加载发票
  2. 获取AccountingId
  3. 将其保存在发票上
  4. 上面几乎是选项3。我很想传递存储库和服务,但这确实只是一个更实用的风格 - 上面的内容也适用于私有字段。

答案 2 :(得分:0)

对于给定的invoiceId,会计BC应始终返回相同的accountingSoftwareId。

如果在第一轮中,呼叫是在会计BC上进行的,但发票更新失败,则帐户BC中的状态为t1,发票BC中的状态为t0。当您重试该命令时,它将执行相同的调用并返回相同的ID,如果更新成功,则您在每个BC中处于t1状态。在最坏的情况下,即使必须手动解析命令,对于给定的发票ID,结果帐户ID也始终相同。

因此,要解决特定发票的会计ID,您可以直接询问会计BC。