DDD领域驱动设计实战(四)-值对象

0 前言

值对象也是领域模型中的领域对象。

应尽量使用值对象建模而非实体。即便一个领域概念必须建模成实体,在设计阶段也应更偏向于将其作为值对象。因为更容易创建、测试、使用、优化和维护。

1 为什么使用值对象?

曾经我们都滥用实体建模。在用户和权限等概念进入协作领域前,实体建模并没有带来什么坏处。在项目启动时,釆用了常用的建模方式:将领域模型中所有属性映射到对应的数据库表。并且为所有属性创建setter/getter。由于每个对象都有一个数据库主键,各个实体被组织在了一个庞大且复杂的对象网。这种建模方式是一种数据建模方式,很大程度受关系型DB影响,认为所有都需范式化,并通过外键关联引用。但其实全然面向实体的思维方法不仅没必要,而且还浪费开发时间。

在将领域概念建模成值对象时,应将通用语言考虑在内,这是建模值对象的首要原则。

那如何确定一个领域概念是否应该建模成一个值对象呢?

2 值对象的特征

  • 度量或描述了领域中的一件东西
  • 可作为不变量
  • 将不同的相关的属性组合成一个概念整体(Conceptual Whole)
  • 当度量和描述改变时,可以用另一个值对象予以替换
  • 可以和其他值对象进行相等性比较
  • 不会对协作对象造成副作用

当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。
需要将值对象看成不变对象,不要给它任何身份标识, 还应尽量避免像实体对象一样的复杂性。

在设计得当时,我们可创建和传递值对象实例,甚至在用完后直接扔了。无需担心客户端对值对象的修改。一个值对象的生命周期可长可短,就像个无害的红细胞在系统中来往。

《实现领域驱动设计》对值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。DDD中描述领域的特定方面,并且是一个没有标识符的对象。

值对象本质上就是一个集。该集合有若干如下属性

  • 描述目的
  • 具有整体概念
  • 不可修改

该集合意义是在领域建模过程中,值对象可保证属性归类的清晰和概念的完整性,避免属性零碎。

3 案例

人员实体包括:姓名、年龄、性别及所在省、市、县和街道等属性。这样显示地址相关属性就很零碎。
可将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,该集合就是值对象

4 值对象的形态

4.1 业务形态

值对象是DDD领域模型中的一个基础对象,跟实体一样源于事件风暴所构建的领域模型,都包含若干属性,与实体一起构成聚合。

实体是业务对象,具有业务属性、业务行为和业务逻辑。
值对象只是若干个属性的集合,只有

  • 数据初始化操作
  • 有限的不涉及修改数据的行为
  • 基本不包含业务逻辑

值对象的属性集虽然在物理上独立,但在逻辑上仍是实体属性的一部分,以描述实体的特征。

也有部分共享的标准类型的值对象,它们有自己的限界上下文及持久化对象,可建立共享的数据类微服务,比如数据字典。

4.2 代码形态

如果值对象是

  • 单一属性,直接定义为实体类的属性
  • 属性集,设计为类,包含具有整体概念的多个属性,这样的值对象无ID,会被实体整体引用

比如电商系统中的Person用户实体:

  • 有单一属性的值对象,比如id、name
  • 也包含多个属性的值对象,比如address

4.3 运行形态

除数据初始化和整体替换的行为,少有更多业务行为。

比如一个用户实体可有多个收货地址,多地址序列化后可嵌入人员的地址属性。值对象创建后不允许修改,只能用另外一个值对象来整体替换
若将值对象嵌入到实体,有如下方式:

4.3.1 属性嵌入

当引用如下之一:

  • 单一属性的值对象
  • 只有一条记录的多属性值对象的实体

4.3.2 序列化大对象

当引用一或多条记录的多属性值对象的实体时。

  • 以序列化大对象方式形成的人员实体对象,收货地址值对象被序列化成大对象JSON串后,嵌入人员实体

4.4 DB形态

设计值对象是期望转“数据建模为中心”为“领域建模为中心”,减少 DB 表的复杂度。

5 值对象简化DB的最佳实践

传统数据建模大多根据数据库范式设计,每个数据库表对应一个实体,每个实体的属性值用单列存储,一个实体主表会对应N个实体从表。
而值对象简化了DB设计,多采用反范式,值对象的属性值和实体对象的属性值保存在同一DB实体表

比如人员和地址,要设计实体和数据模型,有如下解决方案:

  1. 把地址值对象的所有属性放入人员实体表,创建人员实体、人员数据表
    会破坏地址的业务含义和概念完整性
  2. 创建人员和地址两个实体,同时创建人员和地址两张表
    增加了不必要的实体和表,需要处理多个实体和表的关系,导致数据库复杂性剧增

有没有一种设计可使得业务含义清晰,又不让数据库变复杂?综合以上方案优势,扬长避短:

  • 领域建模时,把地址作为值对象,人员作为实体,即可保留地址的业务含义和概念完整性
  • 数据建模时,将地址的属性值嵌入人员实体数据库表,只创建人员数据库表。这既可兼顾业务含义和表达,又不会复杂化DB

值对象就是通过该方式,简化DB设计:

  1. 领域建模时,将部分对象设计为值对象,保留对象的业务含义,同时又减少了实体数量
  2. 数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化DB设计

要发挥对象的威力,就需优先领域建模,弱化DB作用,只把DB作为一个保存数据的仓库。即使违反DB设计原则,也不必大惊小怪,只要业务能顺利运行,无伤大雅。

分析

虽然优势是可简化DB复杂度。但若使用不当,优势就会成劣势。所以必须理解值对象的适用场景。

值对象采用序列化大对象的方式简化DB设计,减少实体表的数量,可简单、清晰表达业务概念。该方式虽然降低DB设计复杂度,却无法满足基于值对象的快速查询,导致搜索值对象的属性值变难。

值对象采用属性嵌入的方式提升了DB性能,但若实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务含义,操作也不方便。

所以对照优劣势并结合实际业务场景,才能发挥值对象的最大作用。

6 实体 V.S 值对象

主要区别如下:

  • 实体有唯一性,值对象没有。比如用户具有唯一性,一旦某用户被系统管理,它就被赋予了在事件、流程和操作中被唯一识别的能力
  • 实体着重唯一性和延续性,不在意属性的变化,属性全变了,它还是自己;值对象着重描述性,对属性变化敏感,属性变了,它就不是自己了
  • 实体和值对象也可能随着系统业务关注点的不同而更换位置。比如,如果另一个限界上下文更关注地址,而不关注与这个地址产生联系的人员,那就把地址设计成实体,人员设计成值对象

比如多人的单位地址是一样的,怎么处理:

  • 许多人可能属同一地址
  • 许多地址也可能属同一人

所以人和地址既可分别作为实体而把对方作为值对象,也可共同作为实体描述业务,这正是业务设计的意义,而不是非黑即白。

DDD提倡从领域模型设计出发,而非先设计数据模型。
传统数据模型设计通常一个表对应一个实体,一个主表关联多个从表,当实体表太多,就很容易陷入复杂DB设计,领域模型就很容易被数据模型绑架。
在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。
设计数据模型时

  • 地址值对象可作为一个属性集整体嵌入人员实体
  • 也可以序列化大对象的形式加入人员的地址属性

同样一个对象在不同场景,可能设计不同:

  • 地址会被某一实体引用,只描述实体,并且其值只能整体替换,这时就可将地址设计为值对象,比如收货地址
  • 地址会被经常修改,地址作为一个独立对象存在,这时应设计为实体,比如行政区划中的地址信息

参考

  • 实体和值对象:从领域模型的基础单元看系统设计
  • 《实现领域驱动设计》
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
领域驱动设计(Domain-Driven Design,DDD)是一种软件开发方法,旨在将复杂的软件系统分解为多个领域,并将每个领域的业务逻辑提取出来进行分析和设计。除了DDD领域驱动设计,还有以下几种常见的软件设计方法: 1. 面向对象设计(Object-Oriented Design,OOD):面向对象设计是一种将系统分解为对象的软件设计方法,强调对象之间的关系和交互,倡导封装、继承和多态等概念。 2. 面向服务设计(Service-Oriented Design,SOD):面向服务设计是一种将系统分解为服务的软件设计方法,强调服务之间的松耦合和可重用性,倡导将系统功能模块化,以服务为中心构建系统。 3. 面向接口设计(Interface-Oriented Design,IOD):面向接口设计是一种将系统分解为接口的软件设计方法,强调定义清晰的接口和规范的通信协议,以便不同模块之间可以相互协作和交互。 4. 领域特定语言设计(Domain-Specific Language Design,DSL):领域特定语言设计是一种通过定义特定领域的语言和规则来描述系统的软件设计方法,强调使用领域专用的语言和工具来描述系统的业务逻辑和规则。 除了上述常见的软件设计方法外,还有许多其他方法和技术,如面向数据设计、面向测试设计、面向切面设计等,可以根据具体的项目需求和情况选用不同的设计方法来进行软件系统的开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值