领域驱动设计:服务和聚合中的领域规则
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
包含 AccountFrom
和 AccountTo
,类似于(对不起,我在这里使用 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 对象交互,进行适当的借记和贷记”.以上是关于领域驱动设计:服务和聚合中的领域规则的主要内容,如果未能解决你的问题,请参考以下文章