DDD单根 聚合根 实体 值对象
前言
2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD。快二十年的时间,领域驱动设计在不断地发展,后微服务时代强调的东西,在国外大家都热衷于领域驱动设计解决业务复杂度,在国内吧,我发现除了大厂以外,你和他说,完全不明白,可能很多人对于什么是面向对象开发,都不明白,什么才是真正的面向对象开发;也是在学习中成长着,我建议是从 《设计模式-可复用面向对象软件的基础》 《领域驱动设计:软件核心复杂性应对之道》《实现领域驱动设计》《解构领域驱动设计》 等等这些书看着走,多在项目中实践,就会明白它想给我们创建一个怎样的软件,如何用领域驱动设计应对当今这些复杂的业务逻辑。
首先都是思想(不是技术),我们要明白;所以它很抽象,很难总结出一套方法解构论,就是很抽象,抽象的东西理解起来就很困难,这就是为什么国内一直 想用,但非常难;其实到 解构领域驱动设计 这本书 21 年出版的,我渐渐地发现吧,已经在往“八股文”方向套了;要不然你看前两本书 全是概念,除了大神还可以摸索出来,但是一般人使用了反而更拉。
可以推荐大家 一个学习ddd的网站的,国内的。
https://www.jdon.com/ddd.html
什么是DDD
领域驱动设计(Domain-Driven Design,简称DDD)
业务初期,我们的功能大都非常简单,普通的CRUD就能满足,此时系统是清晰的。随着迭代的不断演化,业务逻辑变得越来越复杂,我们的系统也越来越冗杂。模块彼此关联,谁都很难说清模块的具体功能意图是啥。修改一个功能时,往往光回溯该功能需要的修改点就需要很长时间,更别提修改带来的不可预知的影响面。
原型 实体 值对象 ,构成聚合根,都在一个领域里面的。 主要是将领域划边界,方法和领域共存, 然后控制出 领域里面的行为 (方法) 其实将业务服务打的更散,放到对象中, 后面的变化不断应对,后期维护是相当快的。 贫血模型 和充血模型, 这个概念后面说一下。

在刚开始开发的时候,我们是这么设计的,随着我们的业务不断发展,我们的项目不断扩大,同时我们的表也是一个订单大表,包含了非常多字段。在我们维护代码时,牵一发而动全身,很可能只是想改下商品的功能,却影响到了创单核心路径。虽然我们可以通过测试保证功能完备性,但当我们在订单领域有大量需求同时并行开发时,改动重叠、恶性循环、疲于奔命修改各种问题。
上述问题,归根到底在于系统架构不清晰,划分出来的模块内聚度低、高耦合。
订单商品模块,假设我们随着数据库区设计:
首先建立模型:
class goods{String id;//主键String skuId;//唯一识别号String goodsName;Bigdecimal price;Category category;//分类List<Specification> specifications;//规格 ...
}class Order{String id;//主键String orderNo;//订单号List<OrderItem> orderItems;//订单明细BigDecimal orderAmount;//总金额...
}class OrderItem{String id;Goods goods;//关联商品BigDecimal snapshotPrice;//下单时的价格
}
考虑到了订单要保存下单时候的价格(当然,这是常识)但这么设计却存在诸多的问题。在分布式系统中,商品和订单这两个模块必然不在同一个模块,也就意味着不在同一个网段中。上述的类设计中直接将Product的列表存储到了Order中,也就是一对多的外键关联。这会导致,每次访问订单的商品列表,都需要发起n次远程调用。
反思设计,其实我们发现,订单BC的Product和商品BC的Product其实并不是同一个entity,在商品模块中,我们更关注商品的规格,种类,实时价格,这最直接地反映了我们想要买什么的欲望。而当生成订单后,我们只关心这个商品买的时候价格是多少,不会关心这个商品之后的价格变动,还有他的名称,仅仅是方便我们在订单的商品列表中定位这个商品。
重点是,领域设计思路需要去脱离数据库的桎梏,最高的预期是根据界限去完成数据库设计,最次。。不需要数据库来绑架我们的系统设计。业务才是王道,一个架构师的核心价值不仅仅体现在框架的应用上,最关键在于能够将我们的系统设计安排得明明白白。
如何改造
class OrderItem{String id;String productId;//只记录一个id用于必要的时候发起command操作String skuId;String productName;...BigDecimal snapshotPrice;//下单时的价格
}
做了一定的冗余,这使得即使商品模块的商品,名称发生了微调,也不会被订单模块知晓。这么做也有它的业务含义,用户会声称:我买的时候他的确就叫这个名字。记录productId和skuId的用意不是为了查询操作,而是方便申请售后一类的命令操作(command)。
在这个例子中,Order 和 goods都是entity,而OrderItem则是value object(想想之前的定义,OrderItem作为一个类,的确是描述了Order这个entity的一个属性集合)。关于标识,我的理解是有两层含义,第一个是作为数据本身存储于数据库,主键id是一个标识,第二是作为领域对象本身,orderNo是一个标识,对于人而言,身份证是一个标识。而OrderItem中的productId,id不能称之为标识,因为整个OrderItem对象是依托于Order存在的,Order不存在,则OrderItem没有意义。
单根 聚合根
在《解构领域驱动设计》 这本书中 是将聚合作为边界的象征,作为所有领域的入口。
聚合(aggregate)是一种边界’它可以封装_到多个实体与值对象’并维持该 边界范围之内的业务完整性°聚合至少包含_个实体’且只有实体才能作为聚合根(aggregateroot)。 工厂(鱼ctory)和资源库(repository)(参见第17章)负责管理聚合的生命周期。前者负责聚合的 创建’用于封装复杂或者可能变化的创建逻辑;后者负责从存放资源的位置(数据库、内存或者其 他Web资源)获取、添加、删除或者修改聚合。
要访问聚合只能通过聚合根的资源库,这就隐式地划定了边界和 入口,有效控制了聚合内所有类型的领域对象。若聚合的创建逻辑较为复杂或存在可变性’可引入工 厂来创建聚合内的领域对象·
聚台的定义与特征
Eric Evans阐释了何谓聚合(aggregate)模式:“将实体和值对象划分为聚合并围绕着聚合定义 边界。选择-个实体作为每个聚合的根’并允许外部对象仅能持有聚合根的引用。作为_个整体来 定义聚合的属性和不变量’并将执行职责赋予聚合根或指定的框架机制°”这一定义说明了聚合的 基本特征。
聚合是包含了实体和值对象的—个边界。 聚合内包含的实体和值对象形成-棵树’只有实体才能作为这棵树的根°这个根称为聚合 根(aggegateroot)’这个实体称为根实体(rootentity)° □外部对象只允许持有聚合根的引用, 以起到边界的控制作用。 □聚合作为_个完整的领域概念整体’其内部会维护这个领域概念的完整性,体现业务上 不变量约束° □由聚合根统_对外提供履行该领域概念职责的行为方法,实现内部各个对象之间的行为 协作。

类似于

四种领域模型
失血模型
贫血模型
充血模型
胀血模型
修改商品为例来举例模型的概念
class goods{String id;String skuId;//唯一识别号String goodsName;
}
失血模型**:略过,可以理解为所有的操作都是直接操作数据库。
贫血模型:
class GoodsDao {@AutowiredJdbcTemplate jdbcTemplate;public void updateName(String name,String id){jdbcTemplate.excute("update goods u set u.goods_name = ? where id=?",name,id);}
}class UserService{@AutowiredUserDao userDao;void updateName(String name,String id){userDao.updateName(goodsName,id);}
}
贫血模型中,dao是一类sql的集合,在项目中的表现就是写了一堆sql脚本,与之对应的service层,则是作为Transaction Script的入口。观察仔细的话,会发现整个过程中user对象都没出现过。
充血模型
interface UserRepository extends JpaRepository<Goods,String>{//springdata-jpa自动扩展出save findOne findAll方法
}class UserService{@AutowoirdUserRepository userRepository;void updateName(String name,String id){Goods goods = goodsRepository.findOne(id);goods.setName(name);goodsRepository.save(user);}
}
充血模型中,整个修改操作是“隐性”的,对内存中goods对象的修改直接影响到了数据库最终的结果,不需要关心数据库操作,只需要关注领域对象goods本身。Repository模式就是在于此,屏蔽了数据库的实现。与贫血模型中goods对象恰恰相反,整个流程没有出现sql语句。
涨血模型:
没有具体的实现,可以这么理解:
void updateName(String name,String id){Goods goods = new Goods(id);goods.setName(name);goods.save();
}
我们在Repository模式中重点关注充血模型。
实体 值对象
在领域驱动模型中,战术模型:

实体
实体(entity)这个词被我们广泛使用’甚至过分使用。设计数据库时,我们用到实体, Len Silverston就说:“实体是一个重要的概念,企业希望建立和存储的信息都是关于实体的信息。’’在分解系统的组成部分时’我们用到实体’EdwardCrawley等人就说:“实体也称为部件、模块、 例程、配件等’就是用来构成全体的各个小块°”
一个典型的实体应该具备3个要素:
身份标识;
属性;
领域行为°
根据ID的共同特征’可以定义一个通用的接口:

通用类型和领域类型ID的区别仅在于值是否代表丁业务含义。作为实体的身份标识,它们都 具有业务价值
实体的属性用来说明主体的静态特征,并持有数据与状态。通常,我们会依据粒度的粗细将 属性分为原子属性与组合属性。定义为开发语言内建类型的属性就是原子属性’如整型、布尔型、 字符串类型等,表述了不可再分的属性概念。
领域行为
实体拥有领域行为,可以更好地说明其作为主体的动态特征。
值对象
值对象(valueohject)通常作为实体的属性,也就是亚里士多德提到的分量、性质、关系、场 所、时间、位置姿态等范畴。正如Eirc Evans所说,“当我们只关心一个模型元素的属性时,应把 它归类为值对象°我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。值对 象应该是不可变的。不要为它分配任何标识′而且不要把它设计成像实体那么复杂 。“
值对象与实体的本质区别
一个领域概念到底该用值对象还是实体类型,第一个判断依据是看业务的参与者对它的相等
判断是依据值还是依据身份标识°—前者是值对象’后者是实体。
值对象具有的特性:
对象创建以后其状态就不能修改;
对象的所有字段都是final类型;
对象是正确创建的(创建期间没有this引用溢出)。
领域行为 : 值对象的名称容易让人误会它只该拥有值’不应拥有领域行为。
实际上,只要采用了对象建 模范式,无论实体对象还是值对象,都需要遵循面向对象设计的基本原则,如信息专家模式,将操 作自身数据的行为分配给它。EircEvans之所以将其命名为值对象,是为了强调对它的领域概念身 份的确认,即关注重点在于值。

微服务架构中的DDD应用
在微服务架构中,我们提倡的是低耦合,高内聚,那么需要达到低耦合高内聚这个目标,我们需要去如何应用DDD领域的概念去完成呢?
在DDD领域中,提供了我们一个非常有意思的东西,叫做界限上下文.界限上下文是怎么来的,我们肯定需要知道,我们要理解一个领域的概念。
以服务端而言,我们需要来界定领域,这时候我们需要来对需求文档进行分析:
根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;
进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;
对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
为聚合根设计仓储,并思考实体或值对象的创建方式;
在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。
领域
现实世界中,领域包含了问题域和解系统。一般认为软件是对现实世界的部分模拟。在DDD中,解系统可以映射为一个个限界上下文,限界上下文就是软件对于问题域的一个特定的、有限的解决方案。
那么我们的服务端假设只有两个领域--一个是订单,一个是商品,那么我们可以把商品领域进行进一步的细分:
假设商品需求如下(事实上这个和用户角色进行了挂钩,我们先不用去太在意这个,先来理解下领域):
买方商品--可见购买商品,购买者可以看到所有商品(进行价格排序)。
卖方商品--可以去上架商品,上架成功之后购买者就能够看到商品。
供应商商品--可以给销售者提供商品,销售者的商品需要在销售列表中可被选择。
在每一个边界就形成了界限上下文。
在进行上下文划分之后,我们还需要进一步梳理上下文之间的关系。
康威(梅尔·康威)定律任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。
康威定律告诉我们,系统结构应尽量的与组织结构保持一致。这里,我们认为团队结构(无论是内部组织还是团队间组织)就是组织结构,限界上下文就是系统的业务结构。因此,团队结构应该和限界上下文保持一致。
拓展:墨菲定律--每当你觉得可能会发生的时候,这件事一定会发生。
通过我们界限上下文的划分,我们可以开始对商品服务内部进行处理:
import com.dn.goods.bussiness.buyer.;//买方上下文
import com.dn.goods.bussiness.seller.;//卖方上下文
import com.dn.goods.bussiness.supplier.*;//供应商上下文
整个DDD领域驱动设计,国内还不够成熟,对于小型的项目,我觉得可以使用来作为实验,因为很多时候,你在说撒,可能别个都不懂,这就很尴尬了,更不用说是开发东西了。
相关文章:

DDD单根 聚合根 实体 值对象
前言2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD。快二十年的时间,领域驱动设计在不断地发展,后微服务时代强调的东西,在国…...

SpringMvc介绍。
目录 1、SpringMvc概述 1、基本介绍 2、工作流程 3、bean加载控制 二、请求 1、请求映射路径 2、请求方式 3、请求参数 4、请求参数(传递json数据) 5、日期类型参数传递 三、响应 四、REST风格 1、REST简介 2、RESTful入门案例 3、RESTfu…...
华为OD机试 - 最小传递延迟(JS)
最小传递延迟 题目 通讯网络中有N个网络节点 用1 ~ N进行标识 网络通过一个有向无环图进行表示 其中图的边的值,表示节点之间的消息传递延迟 现给定相连节点之间的延时列表times[i]={u,v,w} 其中u表示源节点,v表示目的节点,w表示u和v之间的消息传递延时 请计算给定源节点到…...

学生信息管理系统(通讯录)----------通俗易懂、附源码、C语言实现
绪论: 本篇文章使结构体章节后的习题,如果你对C语言有问题,或者结构体有什么问题不妨看看我之前所写的文章(章回体),对于文件管理和内存分配问题我将在后面补上,对于这个学生信息管理系统我用了多种方法和…...

Python抽奖系统
#免费源码见文末公众号# 抽奖系统① def choujiang1():def write():with open(d:\\抽奖系统\\抽奖1.1.pickle,rb) as file:lst1pickle.load(file)namevar1.get()if name not in lst1 and name!录入成功! and name!录入失败! and name!:lst1.append(name)…...

真实景观渲染技巧【Three.js】
受到一些很棒的 three.js 演示、与 covid 相关的旅行禁令以及可能在 pinterest 上花太多时间看美丽的旅行照片的启发——我开始看看我是否可以使用 three.js 和r3f在浏览器中渲染一个令人信服的风景场景。 推荐:将 NSDT场景编辑器 加入你的3D开发工具链。 在过去一个…...

MySQL知识汇总:MySQL函数CASE WHEN用法详解
Case When的两种简单用法 用法一: CASE seasonWHEN Spring THEN 春天 WHEN Summer THEN 夏天 WHEN autumn THEN 秋天 else 冬天 end 用法二: CASE WHEN season Spring THEN 春天WHEN season Summer THEN 夏天WHEN season autumn THEN 秋天 els…...

Python学习-----模块1.0(模块的简介、定义与使用)
目录 前言: 1.什么是模块 2.模块的分类 (1)内置模块 (2)第三方模块 (3)自定义模块 3.模块的使用 4.自定义模块 5.模块和执行文件的判断 前言: 今天就开始讲Python中的模块篇…...

Linux进程学习【二】
✨个人主页: Yohifo 🎉所属专栏: Linux学习之旅 🎊每篇一句: 图片来源 🎃操作环境: CentOS 7.6 阿里云远程服务器 Perseverance is not a long race; it is many short races one after another…...
我问chatGPT,在JavaScript中构造函数和类的区别
问:构造器函数和面向中的类是同样的东西吗|? 答:构造器函数和面向对象中的类并不是同样的东西,它们之间有些许不同。 在面向对象编程中,类是一种抽象的概念,它描述了一类具有相同属性和行为的对象。类可以…...

软考高级-信息系统管理师之沟通管理(最新版)
项目沟通管理 1、项目沟通管理基础项目沟通管理的重要性项目沟通管理相关理论2、规划沟通管理3、管理沟通4、控制沟通项目沟通管理的技术和工具1、项目沟通管理基础 项目沟通管理的重要性 1、与1T项目成功有关的最重要的四个因素是:主管层的支持、用户参与、有经验的项目经理…...

PyQt5 自定义富文本编辑器
介绍 一款使用PyQt5和网页端框架wangEditor集成的富文本编辑器 代码片段 PyQt5客户端 与网页端建立连接def create_connect(self):self.web_view QWebEngineView()self.bridge JSBridge(self.web_view.page())self.web_view.load(QUrl.fromLocalFile(self.editor_path))w…...

【高可用系统架构设计】SLA服务可用性4个9是什么意思?如何保证服务的高可用性 HA(High Availability)?...
如何保证服务的高可用性 HA(High Availability)?高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。方法论上,高可用是通…...
微服务架构设计模式-(14)面向生产环境
生产环境要求 应用安全 数据权限 服务可配置性 不同环境的配置不一样,不能写死代码,所以要配置 可观测性 需要日志系统 应用安全 四个方面 身份验证 验证主体的身份解决方案 单体 cookie 微服务中 API Gateway 访问令牌 不透明令牌透明令牌ÿ…...
HTML5新增用法
新增语义化标签 并无特殊含义,是语义!语义!语义! <header> 头部区域 <nav> 导航区域 <main> 主体区域 <article> 内部标签 <section> 块级标签 <aside> 侧边栏标签 <footer> 尾部…...

富足金字塔:人的努力是为了扩大选择的范围
人的努力是为了扩大选择的范围,这是熵减的另一种表述。富足金字塔代表着人生的三重境界。第一层是温饱。人需要食物、水、住所。第二层是品质。能源、ICT、教育带来更有品质的生活,如智能门锁、智能马桶、扫地机、洗碗机、洗衣烘衣机。第三层是梦想。包括…...

C++类基础(十七)
类的继承——补充知识 ● public 与 private 继承(C Public, Protected and Private Inheritance) 改变了类所继承的成员的访问权限 //公有继承 struct Base { public:int x; private:int y; protected:int z; }; struct Derive : public Base //公有继承…...

LeetCode刷题复盘笔记—一文搞懂贪心算法之56. 合并区间(贪心算法系列第十四篇)
今日主要总结一下可以使用贪心算法解决的一道题目,56. 合并区间 题目:56. 合并区间 Leetcode题目地址 题目描述: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间…...

Andriod入门级开发
这学期有个课设,我们组我负责一个手机APP的开发,虽然刚开始说要实现什么智能导航,类似高德地图那种,但最后阉割的只剩一个Socket通信了,因为之前没有接触过(可能之后也不会再接触),记…...

DCL 数据控制语言
1、简介 DCL英文全称是Data Control Language(数据控制语言),用来管理数据库用户、控制数据库的访问权限。 2、管理用户 2.1 查询用户 select * from mysql.user;查询的结果如下: 其中 Host代表当前用户访问的主机, 如果为localhost, 仅代表只能够在当前本机访问…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...