⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 www.cnblogs.com/xishuai/p/ddd-entity-value-object.html 「田园里的蟋蟀」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

消息场景:用户 A 发送一个消息给用户 B,用户 B 回复一个消息给用户 A。。。

现有设计:消息设计为实体并为聚合根,发件人、收件人设计为值对象。

三个问题:

  1. 实体最重要的特性是什么?
  2. Message 实体是怎么得来的?
  3. 发件人、收件人为什么不是实体?

1. 实体最重要的特性是什么?

《领域驱动设计》5.2 实体:

摘录一段:许多对象不是由它们的属性来定义,而是通过一系列的连续性(continuity)和标识(identity)来从根本上定义的。

归纳:

  • 标识(identity)
  • 连续性(continuity)

标识在实体中的另一种体现就是唯一和不可变,其概念在很多资料中有说明,这也是实体最重要的特性。

我有一个双胞胎哥哥,我们俩出生的时候,长得一模一样,以至于我们的爸妈都分不清,不得已他们在我们脖子上系个项链来标记:谁是老大?谁是老二?其实这个“标记”就可以看作是实体的标识,只不过是用项链来标识的,就像我们在项目中使用 GUID 方式一样,目的就是用来体现标识,但不管用什么方式表示,这个标识必须在这个特定环境下唯一,也就是说,我和我双胞胎哥哥的项链不能完全一样,要不然我爸妈就不能区分我们俩了。

我和我那双胞胎哥哥就这样一天一天的长大,但出奇的是,我们哥俩越长越像,以至于我们互相看对方,都以为自己在“照镜子”一样,但唯一不变的是我们俩脖子上的项链,这也是区分我们哥俩的唯一方式。刚出生的我和现在的我,脖子上的项链是一样的,这也就是实体标识的不可变性,也就是说刚出生的我和现在的我是同一个人,项链只不过在我成长的过程中起到“标记”的作用(当然也可以是手带、脚环之类的信物),它会“陪伴”我的一生,这个“陪伴”的过程,可以理解为实体的另一种特性-连续性

有一天,我们镇要统计双胞胎的分布情况,然后调查人员来到我们家,问我们爸妈:“你们家里有没有双胞胎?几对双胞胎?龙凤胎?还是。。。”,然后我爸妈就报上:“一对双胞胎-两个小子”,然后调查人员就做了笔记走了。在这个过程中,他们丝毫没有提及我脖子上的“项链”,虽然它在我爸妈眼里是那么重要(用来标记我们哥俩),但在调查人员眼里却什么都不是,他们只需要知道我和我双胞胎哥哥是什么样的双胞胎就行了,这也就是实体和值对象的根本区别:实体不仅需要知道它是什么?而且还需要知道它是哪个?而值对象只需要知道它是什么?

特定环境下,实体和值对象的区分例子有很多,比如《领域驱动设计》书中所说的“体育场座位例子”和“ Custorm-Address 例子”等等,但大部分都是强调实体的标识特性,却很少提及连续性,那什么是连续性?这部分内容,在《领域驱动设计》中5.2实体章节中最后部分有提及,但都是零碎的概念性文字,如果不注意的话,很容易会被忽略掉。

摘录一段:只要一个对象在生命周期中能够保持连续性,并且独立于它的属性(即使这些属性对系统用户非常重要),那它就是一个实体。

这个内容可以结合上面我和我双胞胎哥哥的例子进行理解,“项链”会陪伴的我一生,这段话可以拆分对应理解:**项链-标识、一生-生命周期、陪伴-连续性。**也就是说连续性不能理解为生命周期,它应该理解为:标识在实体生命周期内体现出连续性

2. Message 实体是怎么得来的?

结合上面实体特性的理解,Message 实体是怎么得来的,就很好理解了,消息场景毫无疑问聚合的是消息,消息实体是怎么得来的?可以换个角度理解:为什么把消息设计为实体?首先看下消息实体符不符合实体的两个特性。

  • 标识(identity):消息场景中消息的区分通过什么?标题?内容?这些都不行,为了保证消息的唯一性,必须使用标识进行区分,而且必须不可变。消息场景中,有可能会出现标题和内容一样的消息,但这却不是同一个消息,就像我和我那双胞胎哥哥,长的一样,却不是同一人,可以这样说:标识的作用就是为了区分,而消息也必须要区分,所以。。。
  • 连续性(continuity):一次我们家吃饭的时候,我一不小心把饭碗给打碎了,然后我妈就痛打了我一顿,她有个做笔记的习惯,记录我们哥俩的日常生活,比如这次需要记录一下:今天打了谁?但当时她打完我之后,却不记得是打了我?还是我哥?然后她就挨个看我们的屁股项链,来确定今天打了谁?这就是标识在生命周期中连续性的部分体现。消息场景中,在某一阶段需要对消息进行处理,这个处理需要通过标识来明确处理的是哪条消息?这个对消息处理过程的体现就是连续性,有时候连续性需要在标识明确的情况下,但还有一种是其自身的生命周期连续性,比如从消息的创建,到管理,再到最后的销毁,这个过程就是消息实体的连续。

上面的分析说明消息实体符合实体的两个特性,也就是说消息可以设计为实体,至于怎么得来的?可以这样理解,消息场景首先考虑的是消息,就像我们家的双胞胎,首先考虑的是我和我那双胞胎哥哥。

3. 发件人、收件人为什么不是实体?

在之前的一篇博文中,园友鼻涕成诗有这样的疑问:联系人作为值对象这一点有点不太理解,好处是什么?我当时是这样回复的:

联系人作为值对象,因为他不在消息系统中存储,是从外部获取的,而且它的存在要依附于消息,在消息系统这个业务场景中,如果脱离了消息,它就没有什么意义,对于消息而言,我只要知道这个联系人的内容是什么就行了,而不需要它具体什么哪个,人?还是邮箱?这个它并不关心,不是说把联系人作为值对象有什么好处,而是在这个业务场景下,这样设计比较合理些。

回复内容现在看来有些牵强,先不讨论对与错,按照上面消息实体的分析模式,在消息场景下,看下发件人、收件人(可以统称为联系人,发件人和收件人有可能为同一联系人)是否具有实体的一些特性。

  • 标识(identity):联系人是否具有标识?也就是说联系人需不需要进行区分?答案当然是要进行区分,要不然收件箱、发件箱就没办法针对收件人、发件人进行标识,而且联系人有可能名称相同,但是两个不同的联系人,也就是说在消息的整个应用场景中,联系人是必须要唯一标识的,不管它扮演的角色是发件人,还是收件人,这个“角色扮演”概念只是针对某一具体消息来说,联系人所存在的意义(在这个消息中,这个联系人是发件人,但在另外一个消息中,有可能是收件人),但相对于整个消息场景,这个联系人标识是唯一的,而且是不可变的
  • 连续性(continuity):这个可能没有消息实体的连续性好理解,联系人的连续性其实是依附于消息实体而言,它如果独立出来,自身在消息场景中,是没有连续性概念的,就比如在创建消息的时候,我需要判断收件人是否存在,存在的话就创建收件人对象,并赋予创建消息的收件人属性,还有就是消息在被阅读的时候,需要判断阅读人是否有阅读权限等等,这一些操作,就体现出联系人的连续性依附于消息实体,但不可否认,联系人的创建、使用、舍弃等操作,都可以理解围绕某一具体消息的生命周期,也就是联系人的连续性,而且在这个过程中,联系人的标识都需要首先被明确

在之前的理解中,联系人设计为值对象的想法是,把联系人看作是一个值,一个依附于消息实体的具体值,我只需要知道这个值就行了,具体体现就是 SenderID 或 RecipientID,其实这个就是联系人的标识,只是当时被两点所迷惑:

  1. 联系人外部存储:在消息场景中,联系人的获取是从外部获得的,也就是说联系人不在消息场景中存储,也不进行管理,只是一个获取操作,这个和一般的实体场景不太一样,但仔细一想,不管它是从哪里获取的,这个不应该在消息场景中所关心,我应该专注于联系人在消息场景中的连续性。
  2. 联系人依附于消息:这个是最重要的迷惑点,或者说是我根本不了解实体和值对象到底应该是什么?联系人独立于消息,在消息场景中,没有任何意义,但不能因为这一点,就把它设计为值对象,有很多实体是依附关系,只要它存在标识和连续性,那它就是实体。

把联系人设计为值对象当然也有“好处”,比如可以减少对联系人的管理,因为如果联系人设计为值对象,那它就是一个值,也就没有对象的概念,但出来混的迟早是要还的,我要加一个用户禁言功能,这个在现有的设计中就不好进行实现。像这种依附性实体的场景也很多,比如购物车应用中的 Order 和 Custorm,Custorm 依附于 Order,这个首先需要明确的是购物车应用场景,如果是其他的场景下,那 Custorm 就不存在依附关系。

我和我双胞胎哥哥出生的时候,在我们的保温箱上,除了需要标明我们两个的”身份“之外,还需要标明我们爸妈的”身份“,具体标识可以用身份证号,这个就像消息实体中的 SenderID、RecipientID 一样,虽然它是一个”值“,但我还需要知道它具体标识的是哪个对象,因为我不仅需要它表示的值是多少,我还需要知道它所代表的对象是哪个,就比如我和我双胞胎哥哥要根据这个身份证号,找到我们的父母一样。

4. 发件人、收件人是值对象?还是实体?

话不言多,总之一句话:发件人、收件人(联系人)需要设计为实体。

消息场景实体和值对象:

  • Message 消息实体和 Contact 联系人实体。

  • 值对象若干(如 MessageState、MessageType 等)。

文章目录
  1. 1. 1. 实体最重要的特性是什么?
  2. 2. 2. Message 实体是怎么得来的?
  3. 3. 3. 发件人、收件人为什么不是实体?
  4. 4. 4. 发件人、收件人是值对象?还是实体?