值对象也是领域模型中的领域对象。
应该尽量使用值对象建模而非实体对象。即便一个领域概念必须建模成实体,在设计时也应更偏向于将其作为值对象容器,而非子实体容器。因为可以非常容易对值对象进行创建、测试、使用、优化和维护。
阅读本文注意思考:应该建模成实体or值对象?如何实现值对象?
曾经,SaaSOvation公司团队滥用实体建模。在用户和权限等概念进入协作领域前,实体建模并没有给他们带来什么坏处。在项目启动时,他们釆用了常用的建模方式,即将领域模型中所有的属性都映射到对应的数据库表。并且为所有属性创建setter/getter。由于每个对象都有一个数据库主键,各个实体被组织在了一个庞大且复杂的对象网。这种建模方式是一种数据建模方式,很大程度受关系型DB影响,认为所有都需范式化,并通过外键关联引用。后来SaaSOvation团队才知道,全然面向实体的思维方法不仅没必要,而且还浪费开发时间。
在将领域概念建模成值对象时,应将通用语言考虑在内,这是建模值对象的首要原则。
如何确定一个领域概念应该建模成一个值对象呢?注意值对象的特征。
当你决定一个领域概念是否是一个值对象时,需考虑它是否拥有以下特征:
当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。需要将值对象看成不变对象,不要给它任何身份标识, 还应尽量避免像实体对象一样的复杂性。
在使用这种方法分析模型时,会发现很多领域概念都可设计成值对象,而非实体对象。
在设计得当时,我们可创建和传递值对象实例,甚至在用完后直接扔了。不用担心客户端对值对象的修改。一个值对象的生命周期可长可短,就像个无害的过客在系统中来往。 从该角度来看待值对象是个很大转变,就像从没有GC的语言转变到有GC语言。
虽然创建一个值对象类型非常简单,但是有时甚至连有经验的DDD开发者都面临难题。 《实现领域驱动设计》对值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。DDD中描述领域的特定方面,并且是一个没有标识符的对象。
值对象本质上就是一个集。该集合有若干用于描述目的、具有整体概念和不可修改的属性。该集合存在的意义是在领域建模的过程中,值对象可保证属性归类的清晰和概念的完整性,避免属性零碎。
实体人员,原包括:姓名、年龄、性别及所在省、市、县和街道等属性。这样显示地址相关属性就很零碎。 就可将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,该集合就是值对象。
值对象是DDD领域模型中的一个基础对象,跟实体一样源于事件风暴所构建的领域模型,都包含若干属性,与实体一起构成聚合。
值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文及持久化对象,可建立共享的数据类微服务,比如数据字典。
代码中有两种形态。如果值对象是
也包含多个属性的值对象,比如address
实体实例化后的DO对象的业务属性/行为都非常丰富,但值对象实例化的对象相对简单。除了值对象数据初始化和整体替换的行为外,很少有其它业务行为。
举个例子,一人员实体可有多通讯地址,多地址序列化后可嵌入人员的地址属性。值对象创建后不允许修改,只能用另外一个值对象来整体替换。 若将值对象嵌入到实体,即有如下方式:
当引用单一属性的值对象或只有一条记录的多属性值对象的实体时
当引用一条或多条记录的多属性值对象的实体时
设计值对象是期望转“数据建模为中心”为“领域建模为中心”,以减少数据库表的数量和表与表间复杂依赖,尽可能简化DB设计,提升DB性能。
传统数据建模大多根据数据库范式设计,每个数据库表对应一个实体,每个实体的属性值用单独列存储,一个实体主表会对应N个实体从表。 而值对象简化了DB的持久化设计,多采用反范式,值对象的属性值和实体对象的属性值保存在同一DB实体表。
还是人员和地址案例,要设计实体和数据模型,通常有如下解决方案:
那有没有设计可使得业务含义清晰,又不会让数据库变复杂?综合这俩方案优势,扬长避短即可:
值对象就是通过该方式,简化DB设计:
也有DDD专家认为,要发挥对象的威力,就需优先领域建模,弱化DB作用,只把DB作为一个保存数据的仓库即可。即使违反DB设计原则,也不必大惊小怪,只要业务能顺利运行,无伤大雅。
虽然优势是可简化DB设计,提升DB性能。但若使用不当,其优势很快会成劣势。必须理解值对象的适用场景。
值对象采用序列化大对象的方式简化了DB设计,减少了实体表的数量,可简单、清晰表达业务概念。该方式虽然降低DB设计复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象的属性值变难。
值对象采用属性嵌入的方式提升了DB性能,但若实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务含义,操作也不方便。
所以对照优劣势并结合实际业务场景,才能发挥值对象的最大作用。
实体和值对象都是微服务底层最基础的对象,一起实现实体最基本的核心领域逻辑。
实体和值对象的目的都是抽象聚合若干属性以简化设计和沟通,有了这一层抽象,我们在使用人员实体时,不会产生歧义,在引用地址值对象时,不用列举其全部属性,在同一个限界上下文中,大幅降低误解、缩小偏差,主要区别如下:
二者在某些场景可互换,因为很难判断到底将领域对象设计成实体or值对象。值对象在某些场景下有高价值,但并非适合所有场景。需根据团队设计开发习惯及优劣分析,才能选择最适合的方案。 比如多人的单位地址是一样的,怎么处理?一方面,许多人可能属于同一个地址,另一方面,许多地址也可能属于同一个人,人和地址既可以分别作为实体而把对方作为值对象,也可以共同作为实体来描述业务,这正是业务设计存在的价值,也是我们赖以生存的生态位,如果业务设计可以非黑即白一板一眼,反倒不需要什么业务架构师了
DDD提倡从领域模型设计出发,而非先设计数据模型。 传统数据模型设计通常一个表对应一个实体,一个主表关联多个从表,当实体表太多,就很容易陷入复杂DB设计,领域模型就很容易被数据模型绑架。所以值对象和实体是相辅相成。
还是那个案例,在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。 设计数据模型时
该案例也可看出,同样一个对象在不同场景,可能设计不同:
参考