这些年,随着软件业的不断发展,软件系统开始变得越来越复杂而难于维护。这时,越来越多的开发团队开始选择实践DDD领域驱动设计。领域驱动设计是一种非常优秀的软件设计思想,它可以非常好地帮助我们梳理复杂业务,解决大规模业务系统的设计开发与更新维护。但是,领域驱动的学习成本却非常高,使得很多同学难于准确地理解DDD,更难于真正落地实际项目的设计编码。为此,我通过这一系列知识分享,让大家真正准确地理解DDD中这些晦涩的概念,特别是让大家理解最终是怎么落地到软件项目的设计开发中的。

今天,我们首先探讨的是在DDD中,让大家最头疼、最晦涩的概念——什么是“值对象”?要理解“值对象”的设计思想,首先我们先来梳理一下领域驱动的核心思想。领域驱动认为,软件的本质就是对真实世界的模拟,软件中所有业务逻辑正确与否,唯一的判定标准就是,是否与真实世界保持一致。如果一致,设计就是OK的,否则用户就会提BUG,或者变更需求。只要理解了这个本质,软件设计就简单了:我们首先理解真实世界的业务,然后将我们对业务的理解形成软件设计。

然而,这里有一个问题,那就是软件是怎么与真实世界对应的呢?这种对应体现在以下三个方面:

1)真实世界有什么事物,软件世界就有什么对象;

2)真实世界中这些事物有什么行为,软件世界这些对象就有什么方法;

3)真实世界中这些事物间有什么关系,软件世界中这些对象就有什么关联。

因此,我们在软件设计时,首先以业务场景为单位,一个一个地分析每个业务场景都有哪些领域对象,以及它们相互之间的行为与关系,形成领域模型。然后再以领域模型为核心,完成软件的设计与开发。

譬如说,我们要设计一套电子商务系统,按照业务需求会划分为很多功能,那么每个功能就是一个业务场景。譬如在“下单”这个业务场景中,在真实世界中都有哪些事物呢?首先有“订单”,可以形成订单对象;每个订单对应一个用户,但一个用户可以有多个订单,所以从订单到用户是一个“多对一”关系;一个用户有多个地址,然而一个订单只能对应一个地址……如下图,我们按照这样的步骤逐一分析领域对象和它们之间的关系。最后,我们对订单有什么操作呢?有“下单”、“支付”、“查看订单”……

这就是领域模型,订单、用户、地址、订单明细等类,都是领域对象。然而,在DDD中还要把领域对象严格区分为“实体”与“值对象”。这时,很多同学就比较晕,什么是实体?什么是值对象?为了准确理解这个地方,我们先看看《领域驱动设计》原著是怎么说的。

实体(Entity):又称为Reference Object,它具备生命周期,并且在生命周期的过程中,其形式和内容都有可能发生变化,但它的标识永远不变。也就是说,每个实体都有一个唯一的标识,用于区分真实世界中的“他”与其他人,比如用户ID。实体是有生命周期的,比如用户的注册与注销;实体中的一切都可能会变,比如小张将自己的账户转让给了李四,但账户ID是不会变的。这个唯一的标识就是主键,以这种形式的设计,用户有用户ID、账户有账户ID、订单有订单ID,就是实体的设计。很显然,这样的设计大家都能够理解,关键是“值对象”。

值对象(Value Object):在《领域驱动设计》这本书中对值对象的描述比较隐晦,但我们大致可以归纳为以下几个特征:对象声明与对象中的属性都是不变的,可以为多个对象所引用与共享(而不是复制),这就是“值对象”。该怎样来理解这几个特征呢?

譬如,在真实世界中,一个用户可以有多个订单,那么在订单查询时,每个订单就是一个订单对象,每个订单对象都有各自不同的订单ID。因此,订单的设计是实体。然而,每个订单都要引用一个用户,也就是指向一个用户对象。那么,同一个用户的多个订单指向的是谁呢?显然,不能将这个用户复制成多个对象,而是所有这几个订单都引用的是这一个对象。也就是说,这里的“用户”对象是值对象。

然而,值对象又必须是不变的,这就是说,如果“用户”是值对象,那么用户及其相关的属性都必须是不变的。但现实情况是,用户及其用户信息都是可变的,我们会对用户信息进行增删改操作。是的,在真实世界中,一切都是在变化,没有什么是不变的。那么,怎么来理解值对象的不变性呢?

在DDD的领域建模中,除了有领域模型以外,还有一个非常重要的设计叫“限界上下文”。领域模型是描述的真实世界,但真实世界又是无限大的,那么领域模型该如何描述真实世界呢?DDD将一个复杂系统的业务划分成很多个限界上下文,然后对每个限界上下文中进行领域建模。这样,每个领域模型都有各自的边界,我只是在这个边界中描述我的业务。这样的设计,就将纷繁复杂的世界“分而治之”,对每个领域模型的分析就变得简单了。

譬如在以上案例中,可以首先将对用户及其用户档案的管理划分成一个上下文,叫“用户上下文”。在这个上下文中,用户及其属性是可变的,要进行增删改的操作,因此“用户”对象的设计是实体。然而,在另一个“订单上下文”中,订单是实体,要对其进行增删改,但订单引用的“用户”以及“地址”就是值对象。也就是说,在订单上下文中,“用户”以及“地址”是只读的,仅仅用于订单对象的引用,而不会对其进行增删改操作。这样,“用户”以及“地址”作为值对象,就体现了它的只读与不变性。

此外,站在开发的角度来说,领域模型最终要落实到软件开发。如果采用微服务的设计,就会将“用户上下文”与“订单上下文”划分为用户微服务与订单微服务。在划分微服务的同时,也会划分数据库,用户微服务有用户数据库,订单微服务有订单数据库。有了这样的设计,用户表必然是设计在用户数据库中,而不是在订单数据库中。当订单微服务要查询订单时,通过调用用户微服务的接口获得订单相关的用户对象。然而,这些用户对象只存在于订单微服务的内存中,是只读的,而不会存储在订单数据库中去增删改。这就是“值对象”的概念及其设计实现。

总而言之,DDD的实体在本上下文中是可读可写的,而值对象是只读的。在一个上下文中的核心业务是对实体的增删改,而值对象是与实体相关联的,仅仅用于实体的引用与查询的对象。值对象的特性是只读,但在实际项目中的表现有2种形式:

1)  对其它微服务中对象的引用,需要调用其它微服务的接口获得,数据不在我本地的数据库中,仅仅存在于内存中,并且不能修改,只能查询;

2)  一些类似类型、种类、类别的字典数据,虽然数据存储在本地数据库中,但没有增删改的功能,只有查询与引用。例如会员等级、积分规则、支付方式,等等。

(待续)

DDD你真的理解清楚了吗?怎么准确理解“值对象”的更多相关文章

  1. DDD 领域驱动设计-“臆想”中的实体和值对象

    其他博文: DDD 领域驱动设计-三个问题思考实体和值对象 DDD 领域驱动设计-三个问题思考实体和值对象(续) 以下内容属于博主"臆想",如有不当,请别当真. 扯淡开始: 诺兰的 ...

  2. [Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)

    一.简要介绍 ABP vNext 框架本身就是围绕着 DDD 理念进行设计的,所以在 DDD 里面我们能够见到的实体.仓储.值对象.领域服务,ABP vNext 框架都为我们进行了实现,这些基础设施都 ...

  3. 从壹开始微服务 [ DDD ] 之八 ║剪不断理还乱的 值对象和Dto

    缘起 哈喽大家周四好,时间是过的真快,这几天一直忙着在公司的项目,然后带带新人,眼看这周要过去了,还是要抽出时间学习学习,这些天看到群里的小伙伴也都在忙着新学习,还是很开心的,至少当时的初衷已经达到了 ...

  4. DDD实战与进阶 - 值对象

    目录 DDD实战与进阶 - 值对象 概述 何为值对象 怎么运用值对象 来看一个例子 值对象的持久化 总结 DDD实战与进阶 - 值对象 概述 作为领域驱动设计战术模式中最为核心的一个部分-值对象.一直 ...

  5. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  6. DDD 领域驱动设计-三个问题思考实体和值对象(续)

    上一篇:DDD 领域驱动设计-三个问题思考实体和值对象 说实话,整理现在这一篇博文的想法,在上一篇发布出来的时候就有了,但到现在才动起笔来,而且写之前又反复读了上一篇博文的内容及评论,然后去收集资料, ...

  7. DDD 领域驱动设计-三个问题思考实体和值对象

    消息场景:用户 A 发送一个消息给用户 B,用户 B 回复一个消息给用户 A... 现有设计:消息设计为实体并为聚合根,发件人.收件人设计为值对象. 三个问题: 实体最重要的特性是什么? Messag ...

  8. DDD 领域驱动设计-Value Object(值对象)如何使用 EF 进行正确映射

    写在前面 首先,这篇博文是用博客园新发布的 MarkDown编辑器 编写的,这也是我第一次使用,语法也不是很熟悉,但我觉得应该会很爽,博文后面再记录下用过的感受,这边就不多说. 阅读目录: 上一篇回顾 ...

  9. 正确理解DTO、值对象和POCO

    今天推荐的文章比较技术化也比较简单,但是对于一些初学者而言,可能也是容易搞混的概念:就是如何理解DTO.值对象和POCO之间的区别. 所谓DTO就是数据传输对象(Data Transfer Objec ...

  10. [0] DDD领域驱动设计(二) 之 值对象

    DDD中实体对象与值对象的解释比较抽象.主要根据持续性与 ID 识别来区分. ID并非某一对象的直观自然属性,而是在分析建模之 后,赋给模型中的实体类,来达到跟踪,区别,存储目的的一个特值. 结合项目 ...

随机推荐

  1. USACO 2023 December Contest, Gold

    Problem 1. Flight Routes 设原图的邻接矩阵为 \(e\),考虑它给我们的矩阵是什么东西. 设 \(d_{i, j}\) 表示 \(i\) 到 \(j\) 的路径数的奇偶性,那么 ...

  2. RPC框架JMH测试-chatgpt自动生成

    本文将介绍如何使用Java的JMH测试框架来测试RPC框架的性能.我们选择了Apache Dubbo作为目标RPC框架,Dubbo是一种高效的远程调用框架,它支持多种传输协议和序列化协议,并且具有很好 ...

  3. 自建互联网档案馆「GitHub 热点速览」

    这两天北京的气温骤降,仿佛在提醒我们冬日的脚步已悄然而至,让人不禁感叹时间的飞逝,一年的时间"转瞬即逝". 如果你想留下互联网上的珍贵瞬间,避免它们消失在 404 错误中.这款开源 ...

  4. Analysis I Chapter 2 - By Professor Terence Tao

    目录 2.1 The Peano Axioms 2.2 Addition - Exercises 2.2 - 2.3 Multiplication - Exercises 2.3 - Method o ...

  5. .NET 开源扁平化、美观的 C/S 控件库

    前言 给大家推荐一个优秀的控件集,它基于 .NET Framework 4.0,采用纯原生开发,不包含任何第三方插件或类库. 该控件集涵盖了常用的窗体和控件,同时还包括工业工具和类 Web 控件.使用 ...

  6. Paimon lookup store 实现

    Lookup Store 主要用于 Paimon 中的 Lookup Compaction 以及 Lookup join 的场景. 会将远程的列存文件在本地转化为 KV 查找的格式. Hash htt ...

  7. 一文彻底弄懂Java的IO操作

    Java 的 IO(输入/输出)操作是处理数据流的关键部分,涉及到文件.网络等多种数据源.以下将深入探讨 Java IO 的不同类型.底层实现原理.使用场景以及性能优化策略. 1. Java IO 的 ...

  8. 弱口令、子域名、md5、伪随机数、目录爆破与CTF实战

    web 21--弱口令爆破&custom iterator 进去要求输入账号密码,账号输入admin,一般来说管理员用户名都会是这个,密码随便输,然后burpsuite抓包 可以看到账号密码在 ...

  9. pydotplus使用

    pydotplus是别的语言嫁接到python里面的,所以绘制要传入字符串形式表示的结构,而没有python的结构对象直接用来画.代码如下: import pydotplus as pdp graph ...

  10. C#-公众号H5页面授权获取用户code、openid、unionid

    一:配置信息 公众号设置: 1:设置 IP白名单(所在的服务器ip).记录公众号APPID和APPsecret; 2:设置 网页授权域名; 二:页面授权----[html中获取code] 1:页面引入 ...