0

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

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

以下是我的想法:

第一种方法:

有一个具有功能的发票AggregateRoot SendToAccounting,并注入域服务/接口,它将发票发送到会计,并在会计软件中检索一些“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();
}
4

3 回答 3

1

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

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

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

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

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

在代码审查中,我会完全拒绝第三种方法——聚合上的设置器是一种代码味道;关键是用更新它的规则来封装状态。

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

假设分布式事务没有吸引力,您可能想回顾一下 Udi Dahan 对可靠消息传递的看法。

于 2016-07-12T23:18:00.247 回答
0

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

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

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

于 2016-07-13T11:18:35.803 回答
0

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

选项 1 是我过去倾向于在我的域对象具有行为的情况下使用的选项。

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

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

  1. 加载发票
  2. 获取会计 ID
  3. 保存在发票上

这几乎是上面的选项3。不过,我很想传入存储库和服务,但这实际上只是一种更实用的风格——上面的方法也适用于私有字段。

于 2016-07-12T22:31:36.073 回答