贫血域模型


这是已经存在了很长时间的反模式之一,但目前似乎特别突出。我正在与 Eric Evans 就此聊天,我们都注意到它们似乎越来越受欢迎。作为适当领域模型的有力推动者,这不是一件好事。

贫血领域模型的基本症状是,乍一看它看起来像真实的东西。有对象,许多以领域空间中的名词命名,这些对象与真正的领域模型所具有的丰富的关系和结构有关。当您查看行为时,问题就来了,您会意识到这些对象上几乎没有任何行为,使它们只不过是一袋袋 getter 和 setter。实际上,这些模型通常带有设计规则,这些规则规定您不应在域对象中放置任何域逻辑。相反,有一组服务对象捕获所有领域逻辑,执行所有计算并使用结果更新模型对象。这些服务位于域模型之上,并使用域模型来处理数据。

这种反模式的根本可怕之处在于它与面向对象设计的基本思想背道而驰。也就是将数据和流程结合在一起。贫血域模型实际上只是一种程序风格设计,正是像我(和 Eric)这样的对象偏执狂从我们在 Smalltalk 的早期开始就一直在抗争的东西。更糟糕的是,许多人认为贫血的对象是真实的对象,因此完全忽略了面向对象设计的意义所在。

现在面向对象的纯粹主义一切都很好,但我意识到我需要更基本的论据来反对这种贫血症。本质上,贫血领域模型的问题在于它们承担了领域模型的所有成本,而没有产生任何好处。主要成本是映射到数据库的尴尬,这通常会导致整个 O/R 映射层。如果您使用强大的 OO 技术来组织复杂的逻辑,这是值得的。但是,通过将所有行为拉到服务中,您实际上最终会使用事务脚本,从而失去域模型可以带来的优势。正如我在P of EAA 中讨论的那样,领域模型并不总是最好的工具。

还值得强调的是,将行为放入域对象中不应与使用分层将域逻辑与持久性和表示职责等事物分开的可靠方法相矛盾。应该在域对象中的逻辑是域逻辑——验证、计算、业务规则——不管你喜欢怎么称呼它。(在某些情况下,您会争论将数据源或表示逻辑放在域对象中,但这与我对贫血的看法是正交的。)

所有这一切的一个混乱来源是,许多面向对象专家确实建议在域模型之上放置一个过程服务层,以形成一个服务层但这并不是使域模型没有行为的论据,实际上服务层倡导者将服务层与行为丰富的域模型结合使用。

Eric Evans 的优秀著作领域驱动设计对这些层有以下说明。

应用层[他的服务层名称]:定义软件应该做的工作,并指导富有表现力的领域对象解决问题。该层负责的任务对业务有意义或与其他系统的应用程序层交互所必需的。该层保持薄。它不包含业务规则或知识,而只是协调任务并将工作委托给下一层域对象的协作。它没有反映业务情况的状态,但可以具有反映用户或程序任务进度的状态。

领域层(或模型层):负责表示业务的概念、业务情况的信息、业务规则。反映业务情况的状态在这里被控制和使用,即使存储它的技术细节被委托给基础设施。这一层是商业软件的核心。

这里的关键点是服务层很薄——所有的关键逻辑都在域层。他在他的服务模式中重申了这一点:

现在,更常见的错误是太容易放弃将行为拟合到合适的对象中,逐渐滑向过程式编程。

我不知道为什么这种反模式如此普遍。我怀疑这是由于许多人没有真正使用过合适的领域模型,特别是如果他们来自数据背景。一些技术鼓励它;例如 J2EE 的实体 Bean,这是我更喜欢POJO域模型的原因之一。

一般而言,您在服务中发现的行为越多,您就越有可能丧失域模型的好处。如果您所有的逻辑都在服务中,那么您就已经失明了。