实体和值对象
实体和值对象是领域模型中的领域对象。
实体
在 DDD 中有这样一类对象,他们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期,我们把这样的对象称为实体。
1. 实体的业务形态
在 DDD 不同的设计过程中,实体的形态是不同的。在战略设计时,实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。
在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。
实体和值对象是组成领域模型的基础单元
2. 实体的代码形态
在代码模型中,实体的表现形式是实体类,这个类包含了实体的 属性和方法,通过这些方法实现实体自身的业务逻辑。在 DDD 里,这些 实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑 则在领域服务中实现。
3. 实体的运行形态
实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会不大相同。但是,由于它们拥有相同的 ID,它们依然是一个实体。比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。
4. 实体的数据库形态
与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体映射到数据持久化对象。
在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一,在某些场景中,有些实体知识暂驻内存的一个运行态实体,它不需要持久化。比如,基于多个价格 配置数据计算后生成的折扣实体。
而在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。比如,用户 user 与 角色 role 两个持久化对象可生成权限实体,一个实体对 应两个持久化对象,这是一对多的场景,再比如,有些场景为了避免数据库的联查、提升系统性能,会将客户 信息 customer 和账户信息 account 两类数据保存到同一张数据库表中,客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。
值对象
值对象相对实体来说,会更加抽象一些。
在 《实现领域驱动设计》 中对值对象的定义是:通过对象属性值来识别的对象,它将多个相关数据组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫做值对象。
也就是说,值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。当度量和描述改变时,可以用另一个值对象予以替换。它可以与其它值对象进行相等性比较,且不会对协作对象造成副作用。
简单来说,值对象本质上就是一个集合。里面有若干个用户描述目的、具有整体概念和不可修改的属性。在领域建模的过程中,值对象可以保证归类的清晰和概念的完整性,避免属性零碎。
人员实体原本包含:姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样显示地址相关的属性就很零碎了,现在,我们可以将 省、市、县和街道等属性,拿出来构成一个 地址属性集合,这个集合就是值对象了。
1. 值对象的业 务形态
值对象是 DDD 领域模型的一个基础对象,它跟实体一样都是来源于事件风暴所构建的领域模型,都包含了若干属性,它与实体一起构成聚合。
实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。而值对象只是若干属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。
2. 值对象的代码形态
值对象在代码中有两种形态。如果值对象是单一属性,则直接定义为实体类的属性;如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。
在上面的代码中, person 这个实体有若干个单一属性的值对象,比如 Id、name 等属性;同时它也包含多个属性的值对象,比如地址 address。