前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DDD领域驱动设计实战(四)-理解值对象

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

作者头像
JavaEdge
发布2021-02-22 16:47:33
6.2K0
发布2021-02-22 16:47:33
举报
文章被收录于专栏:JavaEdgeJavaEdge

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

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

阅读本文注意思考:应该建模成实体or值对象?如何实现值对象?

1 为什么使用值对象?

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

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

如何确定一个领域概念应该建模成一个值对象呢?注意值对象的特征。

2 值对象的特征

当你决定一个领域概念是否是一个值对象时,需考虑它是否拥有以下特征:

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

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

在使用这种方法分析模型时,会发现很多领域概念都可设计成值对象,而非实体对象。

在设计得当时,我们可创建和传递值对象实例,甚至在用完后直接扔了。不用担心客户端对值对象的修改。一个值对象的生命周期可长可短,就像个无害的过客在系统中来往。 从该角度来看待值对象是个很大转变,就像从没有GC的语言转变到有GC语言。

虽然创建一个值对象类型非常简单,但是有时甚至连有经验的DDD开发者都面临难题。 《实现领域驱动设计》对值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。DDD中描述领域的特定方面,并且是一个没有标识符的对象。

值对象本质上就是一个集。该集合有若干用于描述目的、具有整体概念和不可修改的属性。该集合存在的意义是在领域建模的过程中,值对象可保证属性归类的清晰和概念的完整性,避免属性零碎。

3 案例

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

4 不同状态的值对象

4.1 业务形态

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

  • 实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。
  • 值对象只是若干个属性集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立,但在逻辑上仍是实体属性的一部分,用于描述实体的特征。

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

4.2 代码形态

代码中有两种形态。如果值对象是

  1. 单一属性,直接定义为实体类的属性
  2. 属性集合,设计为Class类,Class将具有整体概念的多个属性归集到属性集合,这样的值对象没有ID,会被实体整体引用
  • Person实体有若干单一属性的值对象,比如id、name

也包含多个属性的值对象,比如address

4.3 运行形态

实体实例化后的DO对象的业务属性/行为都非常丰富,但值对象实例化的对象相对简单。除了值对象数据初始化和整体替换的行为外,很少有其它业务行为。

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

4.3.1 属性嵌入

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

  • 属性嵌入形成的人员实体对象,地址值对象直接以属性值嵌入人员实体

4.3.2 序列化大对象

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

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

4.4 DB形态

设计值对象是期望转“数据建模为中心”为“领域建模为中心”,以减少数据库表的数量和表与表间复杂依赖,尽可能简化DB设计,提升DB性能。

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

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

还是人员和地址案例,要设计实体和数据模型,通常有如下解决方案:

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

那有没有设计可使得业务含义清晰,又不会让数据库变复杂?综合这俩方案优势,扬长避短即可:

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

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

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

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

6 值对象的优劣分析

虽然优势是可简化DB设计,提升DB性能。但若使用不当,其优势很快会成劣势。必须理解值对象的适用场景。

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

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

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

7 实体 V.S 值对象

实体和值对象都是微服务底层最基础的对象,一起实现实体最基本的核心领域逻辑。

实体和值对象的目的都是抽象聚合若干属性以简化设计和沟通,有了这一层抽象,我们在使用人员实体时,不会产生歧义,在引用地址值对象时,不用列举其全部属性,在同一个限界上下文中,大幅降低误解、缩小偏差,主要区别如下:

  • 二者都经过属性聚合形成,实体有唯一性,值对象没有。在本文案例的限界上下文中,人员有唯一性,一旦某个人员被系统纳入管理,它就被赋予了在事件、流程和操作中被唯一识别的能力,而值对象没有也不必具备唯一性
  • 实体着重唯一性和延续性,不在意属性的变化,属性全变了,它还是原来那个它;值对象着重描述性,对属性的变化很敏感,属性变了,它就不是那个它了
  • 战略上的思考框架稳定不变,战术上的模型设计却灵活多变,实体和值对象也有可能随着系统业务关注点的不同而更换位置。比如,如果换一个特殊的限界上下文,这个上下文更关注地址,而不那么关注与这个地址产生联系的人员,那么就应该把地址设计成实体,而把人员设计成值对象。

二者在某些场景可互换,因为很难判断到底将领域对象设计成实体or值对象。值对象在某些场景下有高价值,但并非适合所有场景。需根据团队设计开发习惯及优劣分析,才能选择最适合的方案。 比如多人的单位地址是一样的,怎么处理?一方面,许多人可能属于同一个地址,另一方面,许多地址也可能属于同一个人,人和地址既可以分别作为实体而把对方作为值对象,也可以共同作为实体来描述业务,这正是业务设计存在的价值,也是我们赖以生存的生态位,如果业务设计可以非黑即白一板一眼,反倒不需要什么业务架构师了

DDD提倡从领域模型设计出发,而非先设计数据模型。 传统数据模型设计通常一个表对应一个实体,一个主表关联多个从表,当实体表太多,就很容易陷入复杂DB设计,领域模型就很容易被数据模型绑架。所以值对象和实体是相辅相成。

还是那个案例,在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。 设计数据模型时

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

该案例也可看出,同样一个对象在不同场景,可能设计不同:

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

参考

  • 实体和值对象:从领域模型的基础单元看系统设计
  • 《实现领域驱动设计》
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-10-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 为什么使用值对象?
  • 2 值对象的特征
  • 3 案例
  • 4 不同状态的值对象
    • 4.1 业务形态
      • 4.2 代码形态
        • 4.3 运行形态
          • 4.3.1 属性嵌入
          • 4.3.2 序列化大对象
        • 4.4 DB形态
        • 5 值对象简化DB的最佳实践
        • 6 值对象的优劣分析
        • 7 实体 V.S 值对象
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档