领域驱动设计:服务和聚合中的领域规则

Posted

技术标签:

【中文标题】领域驱动设计:服务和聚合中的领域规则【英文标题】:Domain driven design: Domain rules in services and aggregates 【发布时间】:2012-09-10 19:47:41 【问题描述】:

当涉及多个聚合时,我对域执行业务规则的方式表示怀疑。

假设我有帐户和外部帐户聚合:

public class Account 
    public String getId() ...
    public void add (Double amount) 


public class ExternalAccount 
    public String getId() ...
    public void add (Double amount) 

还有这项服务:

public class TransferService implements TransferServiceInterface 
    public void transfer (String AccountId, String ExternalAccountId, Double amount) 
        Account fromAccount = accRepository.get(AccountId);
        ExternalAccount toAccount = extAccRepository.get(ExternalAccountId);

        transferIsValid(fromAccount, toAccount, amount);

        fromAccount.add(-amount);
        toAccount.add(amount);
    

如果传输不符合域规则,transferIsValid 将抛出异常。

如何防止此模型的用户不使用服务并执行以下操作:

    Account fromAccount = accRepository.get(AccountId);
    ExternalAccount toAccount = extAccRepository.get(ExternalAccountId);

    fromAccount.add(-amount);
    toAccount.add(amount);

用户没有使用该服务,也没有使用 transferIsValid(...) 来检查完整性。我认为我的设计存在错误,因为用户不应该做一些无效的事情。我该如何预防?我的设计哪里出错了?

【问题讨论】:

这里的“用户”是什么意思,用户如何在不调用服务的情况下调用您上面列出的代码? 当我提到“用户”时,我指的是使用该域的开发人员。开发人员可能决定不使用该服务并直接使用聚合。除了代码重复之外,不能保证开发人员会执行业务规则。 为什么开发人员在自己创建这些服务时可能决定不使用这些服务? 可能是因为开发领域模型的开发者不是使用它的人。我认为原因无关紧要。关键是有可能,然后模型允许在某些情况下打破业务规则。那不应该发生。 【参考方案1】:

首先:不要使用Add()提现。 DDD 就是关注领域。而且我认为您在与产品负责人交谈时不会说So when I add a negative amount of money to account A, the equal amount will be added to account B。添加Widthdraw 方法。


记住。编码时不涉及任何用户。程序员是。所有的程序员都可以搞砸代码。

关于服务:您无法通过代码来防止这种情况发生。除非提款的唯一有效方法是将其转移到另一个帐户。在这种情况下,您可以更改 Widthdraw() 方法以将另一个帐户作为参数。

除此之外,只需将文档添加到您的Widthdraw 方法,并说明如果涉及两个帐户,则应使用该服务。恕我直言,任何 DDD 开发人员都应该知道应该使用该服务,因为这是我们在 DDD 中做事的方式(你和我都这样做了,下一个具有 DDD 经验的开发人员也应该这样做)。

【讨论】:

【参考方案2】:

业务逻辑应该在域对象中,因此,与其将业务逻辑放在TransferService 中,我认为避免业务逻辑泄漏到Service 的更好方法是创建名为的新实体AccountTransfer 包含 AccountFromAccountTo,类似于(对不起,我在这里使用 C#):

public class AccountTransfer

    Account From  get; set; 
    Account To  get; set; 

    // More properties         

    private bool IsValid(ammount)
    

    public void DoTransfer(int amount)
    
        is (IsValid(ammount))
        
            From.Withdraw(amount);
            To.Add(amount);
        
    

您可能需要对象AccountTransfer 中的更多信息,例如:

    何时转移 什么样的转账:通过visa转账,paypal.... 要将此类填充到数据库中,您需要存储传输历史记录以供以后跟踪。

通过这种方式,您还可以将 IsValid 方法作为私有方法放入 AccountTransfer 中。

【讨论】:

这和使用服务真的没有区别。它仍然无法强制执行(开发人员仍然可以直接使用帐户)。而AccountTransfer 并不是真正的领域模型。它不代表一个实体。根据 DDD 定义,它只是一个名称奇怪的服务。 @jgauffin:为什么您认为 AccountTransfer 不是真正的领域模型?它还包含其他属性,例如何时传输、传输类型...并将此实体填充到数据库中。这也使服务和 AccountTransfer 有所不同 @CuongLe “业务逻辑应该在域对象中” - 如果您的意思是实体,则不一定。似乎不适合任何实体或跨越多个实体的域逻辑可以放置在域服务中。而且 DDD 明智,它仍然算作常规域逻辑。 @guillaume31:如果您让域逻辑不适合任何实体并跨越多个实体,那么您正在制造逻辑泄漏。逻辑应该适合一个实体。如果您的逻辑跨越多个实体,请定义另一个实体来包装它。 我说的是 Eric Evans 关于 DDD 的开创性著作,“领域驱动设计:解决软件核心中的复杂性”,第 1 页。 105 - “服务是技术框架中的常见模式,但它们也可以应用于领域层。” p. 107 - “资金转移域服务:与 Account 和 Ledger 对象交互,进行适当的借记和贷记”.

以上是关于领域驱动设计:服务和聚合中的领域规则的主要内容,如果未能解决你的问题,请参考以下文章

DDD领域驱动设计 - 设计文档模板

应用领域驱动设计规则“仅通过聚合根访问聚合”时如何画线

领域驱动设计中的验证

领域驱动设计

DDD领域驱动设计-DDD概览

领域驱动设计:如何设计具有依赖关系的关系聚合