极客时间已完结课程限时免费阅读

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

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

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

讲述:欧创新

时长18:56大小12.98M

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

实体

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

1. 实体的业务形态

在 DDD 不同的设计过程中,实体的形态是不同的。在战略设计时,实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。你可以这么理解,实体和值对象是组成领域模型的基础单元。

2. 实体的代码形态

在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。在 DDD 里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现。

3. 实体的运行形态

实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的 ID,它们依然是同一个实体。比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。

4. 实体的数据库形态

与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。
在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。在某些场景中,有些实体只是暂驻静态内存的一个运行态实体,它不需要持久化。比如,基于多个价格配置数据计算后生成的折扣实体。
而在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。比如,用户 user 与角色 role 两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。再比如,有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息 customer 和账户信息 account 两类数据保存到同一张数据库表中,客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。

值对象

值对象相对实体来说,会更加抽象一些,概念上我们会结合例子来讲。
我们先看一下《实现领域驱动设计》一书中对值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。
也就说,值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。当度量和描述改变时,可以用另外一个值对象予以替换。它可以和其它值对象进行相等性比较,且不会对协作对象造成副作用。这部分在后面讲“值对象的运行形态”时还会有例子。
上面这两段对于定义的阐述,如果你还是觉得有些晦涩,我们不妨“翻译”一下,用更通俗的语言把定义讲清楚。
简单来说,值对象本质上就是一个集合。那这个集合里面有什么呢?若干个用于描述目的、具有整体概念和不可修改的属性。那这个集合存在的意义又是什么?在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。
这里我举个简单的例子,请看下面这张图:
人员实体原本包括:姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样显示地址相关的属性就很零碎了对不对?现在,我们可以将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,这个集合就是值对象了。

1. 值对象的业务形态

值对象是 DDD 领域模型中的一个基础对象,它跟实体一样都来源于事件风暴所构建的领域模型,都包含了若干个属性,它与实体一起构成聚合。
我们不妨对照实体,来看值对象的业务形态,这样更好理解。本质上,实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。

2. 值对象的代码形态

值对象在代码中有这样两种形态。如果值对象是单一属性,则直接定义为实体类的属性;如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。
我们看一下下面这段代码,person 这个实体有若干个单一属性的值对象,比如 Id、name 等属性;同时它也包含多个属性的值对象,比如地址 address。

3. 值对象的运行形态

实体实例化后的 DO 对象的业务属性和业务行为非常丰富,但值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外,其它业务行为就很少了。
值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是属性嵌入的方式和序列化大对象的方式。
引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入。引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式嵌入。比如,人员实体可以有多个通讯地址,多个地址序列化后可以嵌入人员的地址属性。值对象创建后就不允许修改了,只能用另外一个值对象来整体替换。
如果你对这两种方式不够了解,可以看看下面的例子。
案例 1:以属性嵌入的方式形成的人员实体对象,地址值对象直接以属性值嵌入人员实体中。
案例 2:以序列化大对象的方式形成的人员实体对象,地址值对象被序列化成大对象 Json 串后,嵌入人员实体中。

4. 值对象的数据库形态

DDD 引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。
如何理解用值对象来简化数据库设计呢?
传统的数据建模大多是根据数据库范式设计的,每一个数据库表对应一个实体,每一个实体的属性值用单独的一列来存储,一个实体主表会对应 N 个实体从表。而值对象在数据库持久化方面简化了设计,它的数据库设计大多采用非数据库范式,值对象的属性值和实体对象的属性值保存在同一个数据库实体表中。
举个例子,还是基于上述人员和地址那个场景,实体和数据模型设计通常有两种解决方案:第一是把地址值对象的所有属性都放到人员实体表中,创建人员实体,创建人员数据表;第二是创建人员和地址两个实体,同时创建人员和地址两张表。
第一个方案会破坏地址的业务涵义和概念完整性,第二个方案增加了不必要的实体和表,需要处理多个实体和表的关系,从而增加了数据库设计的复杂性。
那到底应该怎样设计,才能让业务含义清楚,同时又不让数据库变得复杂呢?
我们可以综合这两个方案的优势,扬长避短。在领域建模时,我们可以把地址作为值对象,人员作为实体,这样就可以保留地址的业务涵义和概念完整性。而在数据建模时,我们可以将地址的属性值嵌入人员实体数据库表中,只创建人员数据库表。这样既可以兼顾业务含义和表达,又不增加数据库的复杂度。
值对象就是通过这种方式,简化了数据库设计,总结一下就是:在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
另外,也有 DDD 专家认为,要想发挥对象的威力,就需要优先做领域建模,弱化数据库的作用,只把数据库作为一个保存数据的仓库即可。即使违反数据库设计原则,也不用大惊小怪,只要业务能够顺利运行,就没什么关系。

5. 值对象的优势和局限

值对象是一把双刃剑,它的优势是可以简化数据库设计,提升数据库性能。但如果值对象使用不当,它的优势就会很快变成劣势。“知彼知己,方能百战不殆”,你需要理解值对象真正适合的场景。
值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。
所以,你可以对照着以上这些优劣势,结合你的业务场景,好好想一想了。那如果在你的业务场景中,值对象的这些劣势都可以避免掉,那就请放心大胆地使用值对象吧。

实体和值对象的关系

实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。
值对象和实体在某些场景下可以互换,很多 DDD 专家在这些场景下,其实也很难判断到底将领域对象设计成实体还是值对象?可以说,值对象在某些场景下有很好的价值,但是并不是所有的场景都适合值对象。你需要根据团队的设计和开发习惯,以及上面的优势和局限分析,选择最适合的方法。
关于值对象,我还要多说几句。其实,DDD 引入值对象还有一个重要的原因,就是到底领域建模优先还是数据建模优先?
DDD 提倡从领域模型设计出发,而不是先设计数据模型。前面讲过了,传统的数据模型设计通常是一个表对应一个实体,一个主表关联多个从表,当实体表太多的时候就很容易陷入无穷无尽的复杂的数据库设计,领域模型就很容易被数据模型绑架。可以说,值对象的诞生,在一定程度上,和实体是互补的。
我们还是以前面的图示为例:
在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。在数据模型设计时,地址值对象可以作为一个属性集整体嵌入人员实体中,组合形成上图这样的数据模型;也可以以序列化大对象的形式加入到人员的地址属性中,前面表格有展示。
从这个例子中,我们可以看出,同样的对象在不同的场景下,可能会设计出不同的结果。有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候你就可以将地址设计为值对象,比如收货地址。而在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。

总结

今天我们主要学习了实体和值对象在 DDD 不同设计阶段的形态,以及它们从战略设计向战术设计演进过程中的设计方法。
这个过程是从业务模型向系统模型落地的过程,比较复杂,很考验你的设计能力,很多时候我们都要结合自己的业务场景,选择合适的方法来进行微服务设计。强调一点,我们不避讳传统的设计方法,毕竟适合自己的才是最好的。希望你能充分理解实体和值对象的概念和应用,将学到的知识复用,最终将适合自己业务的 DDD 设计方法纳入到架构体系,实现落地。

思考题

请用自己的话总结下,实体和值对象的主要区别是什么?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 44

提建议

上一篇
03 | 限界上下文:定义领域边界的利器
下一篇
05 | 聚合和聚合根:怎样设计聚合?
 写留言

精选留言(162)

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

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

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

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

    共 13 条评论
    118
  • nagedb
    2020-09-17
    值对象的设计和使用这样看起来有很大的局限性。

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

    共 8 条评论
    31
  • james.d
    2019-12-19
    在实践中我发现值对象是最容易被误解和忽视的DO,缺乏值对象的建模往往导致模型的不完整,最终系统有缺陷。值对象与实体具有相同的领域规则,是聚合中的重要组成部分,而且也是要参与业务构建的。 值对象在领域内不具备生命周期,领域也不会修改值对象的属性,所以一个值对象在领域内具有不可变性。在该领域中业务不关心该对象的历史和将来,只关心值对象当前的属性对业务的影响。即便值对象的属性包含Id,领域也不会根据该Id去跟踪值对象的变化。 一个领域内的实体在其他领域中可能建模为值对象更合理,值对象一个重要的使用场景是生成快照,用于还原业务现场。比如订单中的商品,地址等等。
    展开
    20
  • FIGNT
    2019-10-21
    实体和值对象都是领域模型的成员,实体是业务唯一性的载体,是个富对象,包含业务逻辑和唯一标识。值对象是属性的集合,没有唯一标识,只是数据的容器,没有业务逻辑。值对象是实体的一部分,为了简化设计,将部分相关属性抽离成值对象。如果值对象变动,原来的值对象可以直接丢弃。也可以理解为值对象是当时数据的快照,只是当时的状态。值对象过多会导致业务的缺失,影响查询性能。具体哪些属性可以作为值对象存在要具体问题具体分析。
    展开

    作者回复: 理解很透彻。

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

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

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

    作者回复: 是的。

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

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

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

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

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

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

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

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

    共 2 条评论
    7
  • Geek_ed5fc0
    2020-05-07
    请教老师,属性嵌入设计表是什么意思,就是把address实体序列化成一个json串,作为user表的一个单独字符串(比如varchar)属性吗

    作者回复: 是的。 当实体引用单一属性值对象或单条记录多属性值对象时,可以采用属性嵌入方式嵌入实体表。当实体引用单条或多条记录的多属性值对象时,可以采用序列化大对象方式嵌入实体表。

    6
  • huaweichen
    2019-10-21
    请问老师,本课程的后面章节,会有关于如何落实DDD的开发实战案例吗?

    作者回复: 会有的,包括事件风暴、从中台的领域建模、到微服务设计都会有案例的,最后会有一个典型案例把这些知识串起来。不过这些案例主要是设计为主,会设计出不同分层的类、服务等,不到具体的代码实现。

    6
  • Romber
    2021-03-24
    想重点说下值对象的缺点: 1. 值对象作为整体嵌入到数据库表中的某一个字段后, 当里面的信息发生变化时(比如人员值对象的人名信息发生变化), 需要修改所有用到人员信息的实体的数据, 哪怕用了领域事件, 负责维护人员实体的聚合发送了领域事件, 其他聚合拿到了变化的消息, 那又怎么样? 假设表中数据上亿, 如何修改? 都不知道该修改哪一条, 用正则表达式吗? 且不说会不会改错, 光那性能问题就够喝一壶的了 2. 如果采用平铺模式, 则可能会让表变的很宽, 几十个字段的宽表, 冗余了很多信息, 经常要做DDL, 大表的DDL真的很烦... 3. 冗余了那么多信息, OLTP数据库通常为行存数据库, 每次查询要查那么多数据, 而且还不一定有用, 浪费IO, 带宽... 4. 业务一直在变, 昨天还是不变的内容, 今天就要能改了, 比如收货地址, 现在像JD这样平台在下完单后是可以修改收货地址的, 序列化一堆值到一个字段以后, 现在要修改, 怎么办? 到时候再独立成一个实体, 就要搬数据, 搬数据那真是风险最高的事情之一了... 5. 值对象是看起来很美, 实际上很难一直维持其定义的一个东西, 他只是要求不能修改, 但他不管现实生活中其实没什么是不能改的, 能不能改还不是产品经理一句话的事, 嵌入到一个表里, 其实是在埋坑, 哪天产品改主意了, 那就哭去吧 6. 值对象在属性数量较少, 确定不会变的属性集合上才有意义, 但这真的很局限 7. 考虑到值对象的各种现存以及潜在风险, 是不是可以考虑把值对象单独建表, 分配主键, 在实体表里保存对应主键值, 通过关联来查询和修改数据, 在DO里也保存值对象的主键值, 不过不暴露值对象的主键值, 开放值对象属性查询接口, 实现按需查询数据库的目的, 同时不开放值对象表的修改接口, 维护值对象的不变性, 等到业务需求发生了变化, 到时把值对象单独建模为一个实体也是很方便的, 因为数据在那, 怎么改都方便, 只是这么做的话感觉不够DDD, 但是更灵活 8. 最后说句题外话, 说是要关注领域建模, 不那么关注数据建模, 说起来容易做起来难, 这不是思维无法转变的问题, 而是现实情况的问题, 数据最终要落地到数据库里, 怎么可能不考虑? 以大型业务来讲, 应用服务作为无状态服务, 扩容起来很简单, K8S多部署几个POD就可以了, 这套技术很成熟, 但是数据库是很难扩展的, 代价比较大, 所以一定要考虑数据库设计的, 不仅要考虑, 还要非常重视, 否则一个不小心把数据库搞挂了, 今年的C你背一下吧... 不知是否可行?
    展开
    共 3 条评论
    5
  • 水如天
    2020-02-09
    老师好,请教下,如果我们需要记录每次值对象的修改记录,并且进行比较。比如,Address这个属性,修改了三次,比较三次地址是如何修改的,这时候adress如何设计呢?

    作者回复: 这样的操作需要在引用值对象的实体建立版本记录,每次修改需要留下这个实体的版本信息。

    5
  • 🌙 飙
    2020-11-30
    有个问题想请教一下,如果把用户的收货地址设计成值对象,以json的格式保存于用户的实体中,假如这个用户有三个收货地址,我要修改其中的地址A,那是要怎么实现呢,(我想到的是,三个都拿出来 ,修改完其中的一个,再整体存回去),这个设计是否不太适用我这个业务场景呢

    作者回复: 收货地址不会有三个的情况吧,快递员到底送到哪个地址?如果有一定要有三个地址,那也应该设计三个地址字段,每个字段分别存储不同的地址数据,每个地址的修改,采用整体替换的方式。

    共 9 条评论
    4
  • 陈四丰
    2020-01-08
    请教欧老师: 如果不同聚合(A1, A2)内的实体(E1, E2)都包含一个值对象(Value Object),该如何处理这个值对象呢? 1, 分别放入E1,E2的各自实体中,会造成代码重复。 2,如果共用,就会增加耦合度。 比如:人(实体)和动物(实体),都有四肢(值对象)。 多谢指点!!!
    展开

    作者回复: 不同聚合的就分开建吧。

    共 2 条评论
    4
  • SkyeDance
    2021-02-22
    实体和值对象最主要的目的是简化设计和沟通,而如果在前期的设计过程中,脑袋中一直想的是实体和值对象的数据库形态的话,未免有点本末倒置了,特别是如果因为数据库表的问题而影响到前期设计的话,更是得不偿失了。 在考虑持久化的问题时,不要把自己的思维仅局限在MySQL等关系型数据库,如果业务不需要保证强事务的话,MongoDB等NoSQL数据库可能更能满足你的需求。
    展开

    作者回复: 是的,在领域建模时主要考虑领域模型,基本不考虑技术实现方案层级的设计。

    3
  • 2020-04-25
    请问老师,我在多个地方看到,尽量避免LIST<值对象> 这样的方式存在,但有时一个实体的某个属性就是多个值对象LIST的时候咋整呢?

    作者回复: 多个list也是可以打成json串存储在实体PO对象的值对象的属性里面的。如果不涉及复杂查询的话,这种方法也不失为一种好的方法,可以简化数据库的设计。 如果实在太多或需要对它进行统计或者查询,设计成实体也是可以的。

    3