让我们从对问题空间的简短回顾开始:DDD的基本原则之一是将业务规则尽可能地靠近需要执行的地方。这是一个非常重要的概念,因为它使您的系统更具“凝聚力”。通常,将规则“向上”移动是贫血模型的标志。其中对象只是数据包,规则将与要执行的数据一起注入。
贫瘠的模型对于从DDD开始的开发人员来说可能很有意义。您将创建一个User
模型,模型EmailMustBeUnqiueRule
将注入必要的信息以验证电子邮件。简单。优雅。问题在于这种“种类”的思考本质上是程序性的。不是DDD。最终发生的事情是,您留下了一个模块,其中包含数十个经过Rules
巧妙包装和封装的模块,但是它们完全缺少上下文,无法更改它们,因为不清楚何时何地执行它们。那有意义吗?这可能是不言而喻的,一个 EmailMustBeUnqiueRule
将在创建的应用User
,但怎么样UserIsInGoodStandingRule
?慢慢但可以肯定的是,提取Rules
脱离他们的上下文会使您拥有一个难以理解的系统(因此无法更改)。仅当实际处理/执行太冗长以致于您的模型开始失去焦点时,才应封装规则。
现在转到您的特定问题:使用Service
/ CommandHandler
引发问题Exception
是您的业务逻辑开始泄漏(“向上”)到您的域之外。为什么您Service
/ CommandHandler
需要知道电子邮件必须是唯一的?应用程序服务层通常用于协调而非实现。如果我们ChangeEmail
向您的系统添加方法/命令,就可以简单地说明其原因。现在,这两种方法/命令处理程序都需要包含唯一检查。这是开发人员可能会试图“提取”的地方EmailMustBeUniqueRule
。如上所述,我们不想走那条路。
一些其他的知识紧缩可以使我们得到更多的DDD答案。电子邮件的唯一性是一个不变性,必须在一组User
对象之间强制实施。您的域中是否存在一个表示“ User
对象集合”的概念?我想您可能可以看到我要去的地方。
对于这种特殊情况(以及更多涉及在集合中强制执行不变式的情况),实现此逻辑的最佳位置将在您的中Repository
。这特别方便,因为您Repository
还“知道”执行这种验证所需的额外基础结构(数据存储)。在您的情况下,我会将此检查放在add
方法中。这有道理吧?从概念上讲,正是这种方法真正User
为您的系统添加了一个。数据存储是实现细节。