DDD 实战课
欧创新
人保资深架构师
55517 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 26 讲
开篇词 (1讲)
DDD 实战课
15
15
1.0x
00:00/00:00
登录|注册

04 | 实体和值对象:从领域模型的基础单元看系统设计

嵌入实体,减少实体表的数量
简化数据库设计
属性嵌入和序列化大对象的方式
数据初始化和整体替换的行为
属性集合设计为Class类
单一属性直接定义为实体类的属性
保证属性归类的清晰和概念的完整性
一个没有标识符的对象
描述领域的特定方面
一对一、一对多或多对一的关系
映射到数据持久化对象
可进行多次修改,但仍是同一个实体
拥有唯一的ID
以DO(领域对象)的形式存在
领域逻辑在实体类的方法中实现
采用充血模型
实体类
组成领域模型的基础单元
多个属性、操作或行为的载体
领域模型中的重要对象
值对象的诞生与实体是互补的
在某些场景下可以互换
实体和值对象是微服务底层的最基础的对象
数据库形态
运行形态
代码形态
业务形态
数据库形态
运行形态
代码形态
业务形态
实体和值对象的关系
值对象
实体
实体和值对象

该思维导图由 AI 生成,仅供参考

你好,我是欧创新。今天我们来学习 DDD 战术设计中的两个重要概念:实体和值对象。
这两个概念都是领域模型中的领域对象。它们在领域模型中起什么作用,战术设计时如何将它们映射到代码和数据模型中去?就是我们这一讲重点要关注的问题。
另外,在战略设计向战术设计过渡的这个过程中,理解和区分实体和值对象在不同阶段的形态是很重要的,毕竟阶段不同,它们的形态也会发生变化,这与我们的设计和代码实现密切相关。
接下来,我们就分别看看实体和值对象的这些问题,从中找找答案。

实体

我们先来看一下实体是什么东西?
在 DDD 中有这样一类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。没理解?没关系!请继续阅读。

1. 实体的业务形态

在 DDD 不同的设计过程中,实体的形态是不同的。在战略设计时,实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。你可以这么理解,实体和值对象是组成领域模型的基础单元。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

领域驱动设计(DDD)中的实体和值对象是两个重要概念。实体具有唯一标识符,延续性和标识跨越软件生命周期,以实体类的形式存在,包含自身的业务逻辑。值对象通过对象属性值来识别,将多个相关属性组合为一个概念整体,没有标识符。在领域建模中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。本文详细介绍了实体和值对象的业务形态、代码形态、运行形态以及数据库形态,以及它们在领域模型中的作用和映射到代码和数据模型中的方式。实体和值对象在某些场景下可以互换,但需要根据团队的设计和开发习惯,以及优势和局限分析,选择最适合的方法。值对象的优势是可以简化数据库设计,提升数据库性能,但如果使用不当,优势会变成劣势。总的来说,实体和值对象在微服务底层是最基础的对象,一起实现实体最基本的核心领域逻辑。值对象的诞生在一定程度上和实体是互补的,它可以简化数据库设计,但需要根据业务场景选择合适的方法进行微服务设计。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《DDD 实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(167)

  • 最新
  • 精选
  • DZ
    陈述一下我的学习心得:实体和值对象的目的都是抽象聚合若干属性以简化设计和沟通,有了这一层抽象,我们在使用人员实体时,不会产生歧义,在引用地址值对象时,不用列举其全部属性,在同一个限界上下文中,大幅降低误解、缩小偏差,两者的区别如下: ①两者都经过属性聚类形成,实体有唯一性,值对象没有。在本文案例的限界上下文中,人员有唯一性,一旦某个人员被系统纳入管理,它就被赋予了在事件、流程和操作中被唯一识别的能力,而值对象没有也不必具备唯一性。 ②实体着重唯一性和延续性,不在意属性的变化,属性全变了,它还是原来那个它;值对象着重描述性,对属性的变化很敏感,属性变了,它就不是那个它了。 ③战略上的思考框架稳定不变,战术上的模型设计却灵活多变,实体和值对象也有可能随着系统业务关注点的不同而更换位置。比如,如果换一个特殊的限界上下文,这个上下文更关注地址,而不那么关注与这个地址产生联系的人员,那么就应该把地址设计成实体,而把人员设计成值对象。

    作者回复: 你太有才了。理解的很透彻。

    2019-10-21
    27
    444
  • stg609
    我对于实体的看法和老师基本一致,但是值对象有补充,愿讨教。 首先,值对象没有id的概念,由其所拥有的所有属性来识别,属性值是不可变的。换句话说就是只要两个对象的所有属性都一样那就认为是同一个对象,可以互相替换, 但改变任何一个属性,就是两个不同的对象。 举个例子,你手里有一张毛爷爷,你不会在意这张毛爷爷是不是之前的那张,你在意的只是它的价值。 但是一个东西是被建模成值对象还是实体,不是一成不变的。 举个例子,汽车是一个实体,那车上的引擎可以认为是值对象,对于汽车而言,引擎坏了,换一个一样的就好了。但是对于引擎厂商来说,引擎就是个实体,厂商需要跟踪每个引擎的一些数据变化,不可能这个引擎丢了,随便拿一个新的引擎就可以替代的。 值对象的好处 既然是DDD, 从基础设施层,如数据库角度去考虑它的好处感觉有些牵强。我认为还是从如何降低业务复杂性角度出发会更合适。 ·很容易判断两个对象是否相等 ·不可变也确保了值对象永远都是正确的,尤其是在并发环境中不会被意外修改,是线程安全的。比如调用 String.ToUpper 会创建一个新的字符串而非修改原来的字符串,这可以避免其他使用了同一字符串的代码出现错误。 ·值对象既然是不可变的,这使得它天然适合被重用,可以提高性能,就好像很多编程语言中的 String 是不可变的,同样的字符串只占用一份空间。 所以,鉴于值对象比实体更轻量级,高性能且线程安全,一般建议总是优先建模值对象,而非实体。 另外,值对象本身虽然是没有id的,但是并不妨碍它的属性是一个实体。

    作者回复: 专业,非常同意你的观点。

    2019-10-29
    15
    126
  • nagedb
    值对象的设计和使用这样看起来有很大的局限性。

    作者回复: 值对象的价值还是挺大的,尤其是在单体完成微服务拆分后,可以实现数据在不同领域模型中的流转。 很多值对象的数据可能来源于其他聚合,它们以数据冗余的方式完成不同领域中数据的流转和共享。在这些聚合中的值对象以实体或聚合根的形式存在,完成数据的集中维护和管理。而在自己的聚合中它则以值对象的形式存在,被聚合内的某一个实体引用。例如:在订单聚合中,订单实体有收货地址这个值对象。在生成订单实体时,会从个人中心的客户聚合中,获取地址实体数据组合成订单聚合的地址值对象。订单实体可以整体引用和修改地址值对象的数据,但不允许单独修改地址值对象的某一个属性数据,如 street。所有地址数据的新增和修改等维护操作,都只能在客户聚合中完成,这样就可以实现业务职责的高内聚,也就是说,如果你要修改某个业务行为,只需要修改一处就可以了。 由于不同聚合中实体和值对象的这种关系,值对象还有一个重要的使用场景,那就是记录和生成业务的数据快照。值对象以数据冗余的方式记录业务发生那一刻前后序聚合之间的业务数据,还原业务发生那一时刻的数据场景。比如订单聚合在下单时会记录订单生成那一刻的商品和收货地址等概要基础数据信息,我们称之为跟单数据。这时订单聚合的商品和收货地址是以包含多个属性的属性集值对象的形式存在的,它们被订单聚合根引用。属性集值对象的设计方式与通过商品 ID 或地址 ID 单一属性值对象关联的方式不同,当商品或地址的源端聚合的商品实体或地址实体数据变更后,不会影响订单聚合中商品和收货地址值对象的快照数据,这样就可以记录业务发生那一刻的业务快照数据了。即使源端商品或地址所在聚合出现服务不可用的情况,也不会影响订单聚合中商品或地址相关的业务逻辑,很好地实现了应用的解耦和故障隔离。

    2020-09-17
    9
    32
  • alpha
    欧老师,你好,我也从事保险行业,在阳光保险工作,这里的值对象指的是什么?值对象是不是意味着数据库里没有对应的表了?

    作者回复: 幸会!幸会! 在聚合的模型中包含聚合根、实体和值对象。聚合根在数据中相当于主表的概念,实体是一般的表,而值对象可以设计成一般表,但是大多数情况下可以依托引用的实体表设计成嵌入属性集或者以Json串的形式存储。 实体就是我们一般理解上的业务对象,我们关注他们的生命周期,所以他们会有全局ID,通过ID来管理追踪它的生命周期。而值对象主要是用于描述的属性集,我们不关注他们的生命周期,更关注它的属性值。比如对于人民币100元,作为发行方央行在判断他是否是假币时,会关注它的ID,通过ID来追踪它的流通轨迹和生命周期。而在流通领域,我们只关注它的价值,不同ID的人民币价值都是100。 为什么会这样设计?我个人感觉主要还是为了实现聚合的解耦。在管理这个实体的聚合中,我们需要通过ID来管理这个实体的生命周期,而当这个实体数据流转到其它聚合时,这个实体的数据值就不允许修改了。这样可以保证一份数据只在一个地方修改,而可以在多个不同的业务领域使用,保证业务的“高内聚和低耦合”。 很多值对象的数据可能来源于其他聚合,它们以数据冗余的方式完成不同领域中数据的流转和共享。在这些聚合中的值对象以实体或聚合根的形式存在,完成数据的集中维护和管理。而在自己的聚合中它则以值对象的形式存在,被聚合内的某一个实体引用。例如:在订单聚合中,订单实体有收货地址这个值对象。在生成订单实体时,会从个人中心的客户聚合中,获取地址实体数据组合成订单聚合的地址值对象。订单实体可以整体引用和修改地址值对象的数据,但不允许单独修改地址值对象的某一个属性数据。所有地址数据的新增和修改等维护,都只能在客户聚合中完成,这样就可以实现业务职责的高内聚,也就是说“如果你要修改某个业务行为,只需要修改一处就可以了。” 由于不同聚合中实体和值对象的这种关系,值对象还有一个重要的使用场景,那就是记录和生成业务的数据快照。值对象以数据冗余的方式记录业务发生那一刻前后序聚合之间的业务数据,还原业务发生那一时刻的数据场景。比如订单聚合在下单时会记录订单生成那一刻的商品和收货地址等概要基础数据信息,我们称之为跟单数据。这时订单聚合的商品和收货地址是以包含多个属性的属性集值对象的形式存在的,它们被订单聚合根引用。属性集值对象的设计方式与通过商品ID或地址ID单一属性值对象关联的方式不同,当商品或地址的源端聚合的商品实体或地址实体数据变更后,不会影响订单聚合中商品和收货地址值对象的快照数据,这样就可以记录业务发生那一刻的业务快照数据了。即使源端商品或地址所在聚合出现服务不可用的情况,也不会影响订单聚合中商品或地址相关的业务逻辑,很好地实现了应用的解耦和故障隔离。

    2020-08-26
    20
  • FIGNT
    实体和值对象都是领域模型的成员,实体是业务唯一性的载体,是个富对象,包含业务逻辑和唯一标识。值对象是属性的集合,没有唯一标识,只是数据的容器,没有业务逻辑。值对象是实体的一部分,为了简化设计,将部分相关属性抽离成值对象。如果值对象变动,原来的值对象可以直接丢弃。也可以理解为值对象是当时数据的快照,只是当时的状态。值对象过多会导致业务的缺失,影响查询性能。具体哪些属性可以作为值对象存在要具体问题具体分析。

    作者回复: 理解很透彻。

    2019-10-21
    5
    17
  • 唐高为
    有身份的是实体,没身份的是值对象。值对象的本质上是“值”。“值”即是一段数据,能存起来就行,无所谓怎么存。值对象的数据变了就不是原来的值对象了;实体的数据变了还是那个实体。

    作者回复: 是的。

    2020-03-07
    11
  • Alvin
    用项目结构来通俗的讲,实体就是我们平时项目中entity包中的类,与数据库表直接映射,值对象文章开头处一直误以为是view层的VO对象,后面才了解它其实就是实体中的属性对象。不知我这么粗浅的领悟对不对

    作者回复: 没错,理解正确。 但是实体在不同的层有不同的形态,如PO,DO,DTO等。实体不一定与数据库表一一对应。

    2019-12-10
    10
  • 密码123456
    实体和值对象,就是把业务拆分,拆分再拆分。直到能够通过“对象”表达某一时刻的业务。实体就是业务中,不可再分割的对象。值对象是对实体的补充。举例:比如网购。商品最重要。商品就是实体,商品的状态比如下单,物流中。只是商品的状态,没那么重要的就是值对象。看到后面才发现,值对象和实体的定义是那么的难!

    作者回复: 抓住几个关键点就不那么难了。比如:实体可修改,值对象不可修改,只可以整体替换。实体是实实在在的业务对象,值对象只是对对象的描述。值对象依附以实体,实体没了值对象也就没了。

    2019-10-22
    2
    8
  • 学到了和以前设计不一样的地方。DDD弱化了数据库设计,减少了表之间的关联关系,将不用来查询的静态值设计为值对象,作为一个字段存储到实体对应的表中。减少了数据库设计的复杂度,避免了复杂的关联。 教数据库的老师估计想打人了😂

    作者回复: 哈哈,出发点不一样,所以观点会有差异。

    2019-10-22
    3
    8
  • 这一章值得深挖的内容很多。 首先,传统的系统设计阶段,更多的是受采用MIS(管理信息系统)学科的影响,在系统设计阶段完成逻辑模型(E-R图)->物理模型(建表)的设计,在系统开发阶段完成具体的编码工作,这个阶段需要完成一个"数据库模型"->"面向对象"模型的转换,这是一个不小的成本。而在实践中,很多项目甚至弱化了面下对象设计,通过各类开发框架又走回了"结构化的编程"的老路,丧失了面向对象设计思想带来的优秀特性,代码再次变得冗余和高耦合。 反过来,我们看DDD方法,系统设计阶段的产出物-领域模型可以近乎无缝的过渡到系统开发阶段(编码阶段),再配合上时下流行的Spring-Data-JPA,给研发效率带来了可观的提升。 以上是我司在DDD实践当中的一些感悟,当然DDD的实践还有很多路要走,也有很多坑要踩...

    作者回复: 一看就是过来人哈。 DDD首先你需要理解它,然后结合自己现状,不断优化并总结出适合自己的DDD设计方法和过程。

    2019-10-29
    2
    7
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部