领域驱动设计 -- 值对象

值对象的定义

简单来说,值对象是对事物的描述,更确切的说,就是对领域对象的描述

描述这个词很好理解,一般都是形容词,比如说美丽的女人,这里美丽就是对女人的外貌描述。也可以是多个形容词的组合,比如说美丽动人可爱的女人,这里的描述就是美丽、动人、可爱三个形容词的组合。

对领域的描述也是如此,有简单的描述,也会有复杂形容词组成在一起的描述,简单的说,就是一个属性字段,复杂描述就是多个属性字段组成的VO,是一个整体数据结构。

不管是简单描述,还是复杂描述,我们都可以理解成值对象,在写代码的时候,我们统一说值对象就是实体的属性。

复杂描述是由多个简单属性组装起来的,多个简单属性描述的方向必须一致,不能随意组装

比如我们上面说的美丽动人可爱的女人,美丽动人可爱三个词全部是用来描述女人的外貌的,所以我们可以新建一个外貌的值对象来描述女人,如果我们在外貌值对象里面新增一个有钱的字段,那就不合适了,有钱和外貌的描述方向完全不同,就不能放在一起。

为什么值对象没有唯一标识

我们在说值对象的时候,我们常常不会在意值对象本身是什么,而是在意值对象在描述什么。DDD 的一些文章经常举下面例子来描述这句话:我们学习数字和字母的时候,不会在意数字和字母是什么,只在在意数字和字母表示什么。

这句话稍微有点点苦涩,简单来说,我不在值对象叫什么名字(唯一标识),我只关心值对象有那些内容组成,描述的是什么。

即使我们给值对象分配了唯一标识,我们也不会关心,因为值对象是对领域的描述,我们只关心描述了什么,只关心描述的具体内容。

还有一个很重要的原因,是因为值对象都是死的,一旦生成,不会再发生变化了,一个不会发生变化的描述,是不需要唯一标识的,不像实体那样,随着业务发生连续性变化时,需要一个唯一标识来追溯这个实体。

我们说值对象没有唯一标识,站的角度是业务领域的角度,如果你站在数据模型上来说,值对象通常是数据库的一条记录,那么给这条记录分配一个唯一标识,如数据库自增 id,则是合理的。即业务角度上值对象没有唯一标识,但数据模型上是有的。

为什么提倡值对象不可变

从业务的角度来说,我们说值对象是对一个事物的描述,一旦这个事物的描述在过去的某一时刻产生,这个描述就不会再产生变化了。

比如说操作记录,记录的是事物在过去的某个时刻发生的事情,一旦发生,就是死的了,不可能再发生变化,所以业务上希望值对象最好是不可变的,这是从业务角度上分析值对象不可变的原因。

技术上来说,如果值对象是可变的,那么就会产生一些问题,最常见的比如引用传值时,值被调用的方法所修改,导致发生一些错误,但比较矛盾的是,引用传值在实际开发中处处可见,大家基本上都会注意到这个错误点,也基本不会去修改值对象,所以不可更改的原因主要还在于业务上。

在实践中,我们可以通过一些技术手段来保证值对象不可变,但实际代码书写的时候,往往并不会要求那么严格,值对象可变有时也不会造成太大的影响,毕竟值对象不可变时,两个相同值对象之间的拷贝还是很繁琐的,在写代码时,我个人也并不会强制要求值对象不可变。

值对象如何持久化

我们主要从三个角度去思考这个问题:

  • 1:需不需要持久化
  • 2:如何持久化
  • 3:被谁触发持久化

需不需要持久化?

值对象是对领域对象的描述,为了保证领域的完整性,领域对象的描述一般是需要持久化的,所以我们认为值对象是需要持久化的。

如何持久化?

一般来说会有两种方式:

1:如果值对象比较复杂,描述的内容很多,描述的内容也比较通用,比如说像操作记录,那么我们可能会单独建表来保存。

2:如果值对象比较简单的话,我们可以单独用一个字段来保存就好了,这个字段挂在描述的实体对应的表上。

值对象应该被谁触发持久化?

值对象是对领域对象的描述,那么领域对象自己的描述信息,就应该由领域对象自己来触发持久化,这个毋庸置疑,因为别人都不知道该什么时候持久化描述信息,只有领域对象自己知道,所以我们认为值对象的描述信息,应该由领域对象去持久化。

这里的领域对象主要说的是实体,实体通过自己的领域能力,将值对象持久化。

聚合很少会去做持久化的东西,聚合只会维护实体之间的固定业务关系,这种固定业务关系往往是不需要落库的,而其他描述基本都是用来描述实体的,几乎没有看到用来描述聚合的值对象,所以聚合进行值对象的持久化也很少见,几乎没有。

值对象大概有那些类型

列举一下工作中常常遇到的值对象:

  • 1:领域对象的描述信息
  • 2:领域层用来解耦的数据载体
  • 3:领域层内部参数的传递

领域对象的描述信息

领域对象的描述信息,上面我们已经说了很多了,最常见的一种。

领域层用来解耦的数据载体

在实际分层中,和领域层交互的只有两层:应用层和基础设置层 ,为了和这两层解耦,我们会在领域层的入口和出口,都自己定义接口,定义接口就需要入参和出参,这些入参和出参就是值对象,比如说应用层调用领域层的参数传递,领域层和基础设施层的参数传递,这些传递都是通过值对象进行传递。

可能这些值对象并不太符合值对象的定义标准,看起来仅仅像一个数据载体的作用,但没有这些值对象承载数据,你的系统是跑不起来的,不是可能,是一定跑不起来。

这些值对象往往是领域行为和领域服务的入参和出参,我们在定义的时候,也是会按照当前我需要什么,我要得到什么来定义的,基本都是按照业务来定义的。

领域层内部参数的传递

这种值对象是完全为了维护系统的运行来定义的,就是一种数据载体,是一种临时的值对象,在场景非常复杂的时候,肯定会运用到,比如说我在进行一些金额的计算,需要值对象来承担中间的计算结果,这时候的值对象就是一种数据载体,没有业务含义的。

在工作中,我们差不多会碰到以上三种值对象,我们在领域建模的时候,会使用到第一、二种值对象,在实际写业务逻辑代码时,会用到第三种值对象。

值对象和其他领域元素之间的关系

值对象是对实体的描述,所以是实体的属性,和实体是多对一的关系。一个值对象一般只会用来描述一个实体,但有些情况下,值对象非常通用,可能会挂在多个实体身上,比如说操作记录值对象,操作记录太通用了,几乎大多数实体重要属性的变更,都会有记录操作记录。

值对象和值对象上从业务上来说,是没有关系的,一个值对象不能被用来描述另外一个值对象,但从系统运行的角度来说,值对象之间是可以嵌套的,比如刚才我们说值对象会保存计算的中间结果,这个中间结果有可能就非常复杂。

END

如果你还不清楚怎么判断值对象,建议从下面三个角度思考:

  • 1:是对领域对象的描述信息。
  • 2:是死的,一旦生成业务不会再发生变化。
  • 3:生命周期只会存在领域层。

PS:上面说实体的简单属性也是值对象,是的,这句话其实是有歧义的,但 DDD 中并没有说简单属性不是值对象,很多简单属性也是对实体的描述信息,如果你觉得不适,你就认为复杂属性组成的 VO 是值对象就好了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值