[DDD读书笔记] 构造块②实体、值对象和服务

892 阅读7分钟

前文回顾

上一篇介绍了该书的第二部分“模型驱动设计的构造块”,将面向对象领域建模中的一些核心的最佳实践提炼为一组基本的构造块。我们了解了模型驱动设计(MODEL DRIVEN DESIGN)导航图,分层架构模式(Layered Architecture),以及作为“反模式”的智能用户界面模式(Smart UI)和事物脚本模式(Transaction Script)。

这一篇我们继续学习该书的第二部分。

如何在程序中表现领域模型

作者提供了几个重要的模型元素:实体(ENTITY)、值对象(VALUE OBJECT)和服务(SERVICE)。

实体(ENTITY)

主要由标识定义的对象被称作ENTITY。ENTITY有特殊的建模和设计思路。它们具有生命周期,这期间它们的形式和内容可能发生根本改变,但必须保持一种内在的连续性。为了有效地跟踪这些对象,必须定义它们的标识。

ENTITY不是由属性来定义的。不同的ENTITY可能存在相同的属性,比如重名的人。而且即使是同一个人,不同时期可能有不同的名字。当然ENTITY不一定非得是人或者实物,它可以是任何事物,只要满足两个条件即可:

一是它在整个生命周期中具有连续性,二是它的区别并不是由那些对用户非常重要的属性决定的。

ENTITY的标识也被称为ID,ID通常是由系统自动生成的。生成算法必须确保ID在系统中是唯一的。一旦这个ID被创建并存储为ENTITY的一个属性,必须确保它永远不变

值对象(VALUE OBJECT)

另一方面,在领域模型中,并不是所有的对象都是具有标识的ENTITY。比如银行的交易,同一天同一个账户发生了两笔金额相同的存款交易,这实际上是两笔不同的交易,所以它们是具有各自标识的两个实体。同时,用于表示交易金额属性的,可能是货币对象的实例,我们没有必要区分这些实例,因而它们不具有标识。

用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象)。VALUE OBJECT被实例化之后用来表示一些设计元素,对于这些设计元素,我们只关心它们是什么,而不关心它们是谁。

VALUE OBJECT与ENTITY之间的根本区别在于:

一个对象是用来表示某种具有连续性和标识的事物的呢(可以跟踪它所经历的不同状态,甚至可以跨不同的实现跟踪它),还是用于描述某种状态的属性。

比方说一个小女孩在画画,她需要选择不同颜色和不同粗细的水彩笔,如果她有两只颜色、粗细相同的水彩笔,她不会在意用哪一只。但是如果问她这些画是谁画的,她很快就能辨认出哪些是自己画的,哪些是姐姐画的。她们的作品具有标识,而水彩笔没有。如果要孩子记住哪根线条是哪只笔画的,那就有点过份了。

由于模型中最引人注意的对象往往是ENTITY,跟踪ENTITY的标识是非常重要的,但为其他对象也加上标识会影响系统性能并增加分析工作,而且会使模型变得混乱,因为所有对象看起来都是相同的。

软件设计要时刻与复杂性做斗争。我们必须区别对待问题,仅在真正需要的地方进行特殊处理。

VALUE OBJECT可以是其他对象的集合,甚至可以引用ENTITY。VALUE OBJECT也可以作为ENTITY的属性。它们经常作为参数在对象之间传递消息。作者指出:

在可能的情况下都要将它们(VALUE OBJECT)设计为不可变的。只要VALUE OBJECT是不可变的,变更管理就会很简单,因为除了整体替换之外没有其他的更改。

共享VALUE OBJECT还可以为作为性能优化的一种方式。 比如在房屋设计软件中,如果每个电源插座都是一个单独的VALUE OBJECT,那么在一所房屋的一个设计版本中可能就会有上百个这种VALUE OBJECT。但如果把电源插座看成是可互换的,就只需共享一个电源插座实例,并让所有电源插座都指向这个实例。

当然,也有一些特殊情况允许VALUE OBJECT的可变。比如值的改变很频繁,创建删除对象的开销很大等等。要注意,如果一个VALUE OBJECT是可变的,那么就不能共享。

在持久化的时候,可以将VALUE OBJECT的副本保存到ENTITY的表中,而不是关联到另一张单独的表。这种用空间换时间的做法被称为非规范化(Denormalization)。

服务(SERVICE)

SERVICE适合用来表示领域中的一些操作或者动作。在软件技术中,有很多东西可以被成为SERVICE,而在领域中也可以使用SERVICE。

当对软件要做的某项无状态的活动进行建模时,就可以将该活动作为一项SERVICE。

需要注意的是,SERVICE不应该用来替代ENTITY和VALUE OBJECT中的行为,应当将独立的操作定义为一个SERVICE。好的SERVICE有以下3个特征。

(1)与领域概念相关的操作不是ENTITY或VALUEOBJECT的一个自然组成部分。

(2)接口是根据领域模型的其他元素定义的。

(3)操作是无状态的。

我们知道在领域层之外,在其他层也存在SERVICE,我们需要明确责任,将它们区分开。基础设施层的SERVICE一般是纯技术的SERVICE,比较好区分,例如发送邮件的SERVICE。但是应用层SERVICE和领域层SERVICE很难区分,这一点作者Eric也承认:

很多领域或应用层SERVICE是在ENTITY和VALUE OBJECT的基础上建立起来的,它们的行为类似于将领域的一些潜在功能组织起来以执行某种任务的脚本。我们在这里会遇到领域层与应用层之间很微妙的分界线。

比方一个银行应用程序提供的转账功能,领域层的SERVICE负责处理转账的转出方和转入方,资金转账在银行领域语言中是一个有意义的操作(或者说是一个银行业术语)。如果将转账交易的记录导出到Excel中,那么这个导出操作就是应用层SERVICE。我们应该根据各层的职责,将SERVICE进行划分:

image.png

ENTITY和VALUE OBJECT往往由于粒度过细而无法提供对领域层功能的便捷访问。作者的经验告诉我们:

在大型系统中,中等粒度的、无状态的SERVICE更容易被复用,因为它们在简单的接口背后封装了重要的功能。

系列文章