DDD全网最通俗易懂讲解(一)

基础概念

领域

领域就是用来确定范围的,范围即边界,这也是DDD在设计中不断强调边界的原因。简言之,DDD的领域就是这个边界内要解决的业务问题域。领域可以进一步划分为子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问拆分为小问题的过程。

其实好理解,领域会细分为不同的子域他们分别是:核心域,通用域和支撑域

决定产品和公司核心竞争力的子域就是核心域,他是业务成功的主要因素和公司的核心竞争力。没有太多个性化的述求,同时被多个子域使用的通用功能子域就是通用域。还有一种功能子域是必须的,但即不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,就是支撑域

通用域例子:比如认证、权限,这些都很容易买到。而支撑域则具有企业特性,但不具有通用性,例如数据代码类的数据字典等

聚合根和领域服务负责封装实现业务逻辑。领域服务负责对聚合更进行调度和封装,同时可以对外提供各种类型的服务,对于不能直接通过聚合根的就需要通过领域服务。

聚合根其实就是本身无法完全处理这个逻辑,例如支付这个逻辑,订单聚合不可能支付,所以在订单聚合上架一层领域服务,在领域服务中实现支付逻辑,然后应用服务调用领域服务。

遵守以下规范:

1.同限界上下文内的聚合之间的领域服务可直接调用

2.两个限界上下文的交互必须通过应用服务层抽离接口->适配层适配

例子,用户升职,上机领导要变,上级领导的下属要变代码如下

限界上下文

限界就是领域的边界,而上下文则是语义环境,通过领域的限界上下文,我们可以在统一的领域边界内用统一的语言进行交流,简单来说限界上下文可以理解为语义环境。

综合一下,我认为限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证领域之内的一些术语,业务相关对象等有一个确定的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确的知道什么应该在模型中实现,什么不应该在模型中实现。

如果没有具体的语义环境,还真不太好理解。但是,如果你已经知道了这句话的语义环境,比如是寒冬腊月或者是炎炎夏日,那理解这句话的涵义就会很容易了。

所以语言离不开它的语义环境。

而业务的通用语言就有它的业务边界,我们不大可能用一个简单的术语没有歧义地去描述一个复杂的业务领域。限界上下文就是用来细分领域,从而定义通用语言所在的边界。

正如电商领域的商品一样,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。看到这,我想你应该非常清楚了,领域边界就是通过限界上下文来定义的。

理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。

可以说,限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。

贫血模型和充血模型

贫血模型

贫血模型具有一堆属性和set get方法,存在的问题就是通过pojo这个对象上看不出业务有哪些逻辑,一个pojo可能被多个模块调用,只能去上层各种各样的service来调用,这样以后当梳理这个实体有什么业务,只能一层一层去搜service,也就是贫血失忆症,不够面向对象。

充血模型

比如如下user用户有改密码,改手机号,修改登录失败次数等操作,都内聚在这个user实体中,每个实体的业务都是清晰的,就是充血模型,充血模型的内存计算会多一些,内聚核心业务逻辑处理。

说白了就是,不只是有贫血模型中setter getter方法,还有其他的一些业务方法,这才是面向对象的本质,通过user实体就能看出有哪些业务存在。

@NoArgsConstructor
@Getter
public class User extends Aggregate<Long, User> {/*** 用户名*/private String userName;/*** 姓名*/private String realName;/*** 手机号*/private String phone;/*** 密码*/private String password;/*** 锁定结束时间*/private Date lockEndTime;/*** 登录失败次数*/private Integer failNumber;/*** 用户角色*/private List<Role> roles;/*** 部门*/private Department department;/*** 用户状态*/private UserStatus userStatus;/*** 用户地址*/private Address address;public User(String userName, String phone, String password) {saveUserName(userName);savePhone(phone);savePassword(password);}/*** 保存用户名* @param userName*/private void saveUserName(String userName) {if (StringUtils.isBlank(userName)){Assert.throwException("用户名不能为空!");}this.userName = userName;}/*** 保存电话* @param phone*/private void savePhone(String phone) {if (StringUtils.isBlank(phone)){Assert.throwException("电话不能为空!");}this.phone = phone;}/*** 保存密码* @param password*/private void savePassword(String password) {if (StringUtils.isBlank(password)){Assert.throwException("密码不能为空!");}this.password = password;}/*** 保存用户地址* @param province* @param city* @param region*/public void saveAddress(String province,String city,String region){this.address = new Address(province,city,region);}/*** 保存用户角色* @param roleList*/public void saveRole(List<Role> roleList) {if (CollectionUtils.isEmpty(roles)){Assert.throwException("角色不能为空!");}this.roles = roleList;}
}

实体和值对象

实体

实体和值对象这两个概念都是领域模型中的领域对象。实体和值对象是组成领域模型的基础单元。

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

实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品

在领域模型映射到数据模型时,一个实体可能对应0个,1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一 。在某些场景中,有些实体只是暂驻在静态内存的一个运行态实体,他不要持久化,比如基于多个价格配置数据计算后生成的折扣实体。

而在有些复杂场景下,实体与持久化对象则可能是一对一或者多对一的关系,比如吗,用户user与角色role两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。再比如,有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息customer和账户信息account两类数据保存到同一张数据库表中客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景,

@NoArgsConstructor
@Getter
public class User extends Aggregate<Long, User> {/*** 用户id-聚合根唯一标识*/private UserId userId;/*** 用户名*/private String userName;/*** 姓名*/private String realName;/*** 手机号*/private String phone;/*** 密码*/private String password;/*** 锁定结束时间*/private Date lockEndTime;/*** 登录失败次数*/private Integer failNumber;/*** 用户角色*/private List<Role> roles;/*** 部门*/private Department department;/*** 领导*/private User leader;/*** 下属*/private List<User> subordinationList = new ArrayList<>();/*** 用户状态*/private UserStatus userStatus;/*** 用户地址*/private Address address;public User(String userName, String phone, String password) {saveUserName(userName);savePhone(phone);savePassword(password);}/*** 保存用户名* @param userName*/private void saveUserName(String userName) {if (StringUtils.isBlank(userName)){Assert.throwException("用户名不能为空!");}this.userName = userName;}/*** 保存电话* @param phone*/private void savePhone(String phone) {if (StringUtils.isBlank(phone)){Assert.throwException("电话不能为空!");}this.phone = phone;}/*** 保存密码* @param password*/private void savePassword(String password) {if (StringUtils.isBlank(password)){Assert.throwException("密码不能为空!");}this.password = password;}/*** 保存用户地址* @param province* @param city* @param region*/public void saveAddress(String province,String city,String region){this.address = new Address(province,city,region);}/*** 保存用户角色* @param roleList*/public void saveRole(List<Role> roleList) {if (CollectionUtils.isEmpty(roles)){Assert.throwException("角色不能为空!");}this.roles = roleList;}/*** 保存领导* @param leader*/public void saveLeader(User leader) {if (Objects.isNull(leader)){Assert.throwException("leader不能为空!");}this.leader = leader;}/*** 增加下属* @param user*/public void increaseSubordination(User user) {if (null == user){Assert.throwException("leader不能为空!");}this.subordinationList.add(user);}
}

值对象

简单来说,值对象本质上就是一个集。那么这个集合里面有什么呢?若干个用于描述目的,具有整体概念和不可修改的属性。那这个集合存在的意义又是什么?在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎

/*** 地址数据*/
@Getter
public class Address extends ValueObject {/*** 省*/private String province;/*** 市*/private String city;/*** 区*/private String region;public Address(String province, String city, String region) {if (StringUtils.isBlank(province)){Assert.throwException("province不能为空!");}if (StringUtils.isBlank(city)){Assert.throwException("city不能为空!");}if (StringUtils.isBlank(region)){Assert.throwException("region不能为空!");}this.province = province;this.city = city;this.region = region;}
}

img

值对象与实体一起构成聚合。值对象逻辑上是实体属性的一部分,用于描述实体的特征,值对象创建后就不允许修改了。只能用另一个值对象来整体替换。值对象是一些不会修改,只能完整替换的属性值的集合,你更关注他的属性你更关注他的属性和值,它没有太多的业务行为,用于描述实体的一些属性集,被实体引用,依附于实体的值对象基本没有自己的数据库表。是否要设计成值对象,你要看这个对象是否后续还会来回修改,会不会有生命周期。如果不可修改,并且以后也不会专门针对它进行查询或者统计,你就可以把它设计成值对象,如果不行,那就设计成实体吧。

在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。

caf56dbbc3474dda9e784ee3c8b724a1.png

能会设计出不同的结果。有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候你就可以将地址设计为值对象,比如收货地址。而在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。

聚合

实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有业务属性和业务行为,而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象

img

你可以这么理解,聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。

聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。

img

聚合根

如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。

其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。

最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。

347d1d2a789a4163975075b3df7eb8dc.png

第一步:采用事件风暴,根据业务行为,梳理出在投保过程中发生这些行为的所有的实体和值对象,比如投保单,标的,客户,被保人等

第二步:从众多实体中年选出适合作为对象管理者的根实体,也就是聚合根。判断一个实体是否是聚合根,你可以结合以下场景分析:是否有独立的生命周期?是否有全局唯一ID,是否可以创建或修改其他对象?是否有专门的模块来管这个实体。图中的聚合根分别是投保单和客户实体

第三部:根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出一个包含聚合根(唯一)多个实体和值对象的对象聚合这个集合就是聚合。在图中我们构建客户和投保这两个聚合

第四步:在聚合内根据聚合根,实体和值对象的依赖关系,画出对象的引用和依赖模型。这里我们需要说明一下:投保人和被保人的数据,是通过关联客户ID从客户聚合中获取的。在投保聚合里他们是投保单的值对象,这些值对象的数据是客户的冗余数据,即使未来客户聚合的数据发生了变根,也不会影响投保单的值对象数据,从图中哦们可以实体之间的引用干洗,比如在投保聚合里投保单聚合根引用了报检单实体,报价单实体则引用了报价规则子实体

第五步:多个聚合根据业务语义和上下文一起划分到同一个限界上下文内

这就是一个聚合诞生的完整过程了

领域事件

领域事件可以是业务流程的一个步骤,比如投保业务缴费完成后,触发投保单转保单的动作,也可能是定时批处理过程中发生的时间,比如批处理生成季缴费通知单,触发发送缴费邮件通知操作,或者一个时间发生后处触发的后续动作,比如密码连续输错三次,触发锁定账户的动作

在做用户旅程或者场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/177206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解二叉树

【本节目标】 1.树的概念和结构 2.二叉树的概念和结构 3.二叉树的顺序结构及实现 4.二叉树的链式结构及实现 1.树的概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它由一个根结点和n(>0)个子树构成&#xff0c;之所以叫做树&#xff0c;是因为它很像生活…

Hive数据库与表操作

文章目录 一、准备工作二、Hive数据库操作&#xff08;一&#xff09;Hive数据存储&#xff08;二&#xff09;创建数据库&#xff08;三&#xff09;查看数据库&#xff08;四&#xff09;修改数据库信息 一、准备工作 二、Hive数据库操作 &#xff08;一&#xff09;Hive数据…

Python Selenium 图片资源自动搜索保存 项目实践

实现访问首页 from os.path import dirnamefrom selenium import webdriverclass ImageAutoSearchAndSave:"""图片自动搜索保存"""def __init__(self):"""初始化"""self.driver webdriver.Chrome(executable_pa…

西南科技大学数字电子技术实验二(SSI逻辑器件设计组合逻辑电路及FPGA实现 )FPGA部分

一、实验目的 1、掌握用SSI(小规模集成电路)逻辑器件设计组合电路的方法。 2、掌握组合逻辑电路的调试方法。 3、学会分析和解决实验中遇到的问题。 4、学会用FPGA实现本实验内容。 二、实验原理 包括:原理图绘制和实验原理简述 1、1位半加器 2、1位全加器 3、三…

2021年全国硕士研究生入学统一考试管理类专业学位联考英语(二)试题

文章目录 2021年全国硕士研究生入学招生考试英语二试题SectionⅠUse of EnglishSection Ⅱ Reading ComprehensionText 1Text 2Text 2Text 3Text 4 Section III TranslationSection Ⅳ Writing 2021年全国硕士研究生入学招生考试英语二试题 SectionⅠUse of English Directio…

oracle数据库备份2(expdp)

使用exp命令定时进行数据库备份的操作前面已经记录过&#xff1a; oralce数据库定时备份 下面记录下使用更加高效的expdp命令和impdp&#xff0c;这两个命令同样是用来做数据库备份和还原的&#xff0c;但速度更快&#xff0c;效率更高&#xff0c;缺点是只能用在服务器端进行…

阿里云ACE认证之国际版与国内版对比!

大厂疯狂裁员&#xff0c;互联网行业迎来寒冬&#xff0c;技术人员被动陷入疯狂内卷。在愈加内卷的IT领域&#xff0c;“云计算”作为少有的蓝海&#xff0c;无疑是打工人未来实现职场提升、摆脱内卷的绝佳选择&#xff01; 对于云计算行业的人来说&#xff0c;最值得考的肯定是…

洪泽湖流域建筑物、人口密度与土地利用数据技术服务

一&#xff0e;背景介绍 人类社会发展离不开土地&#xff0c;没有土地就没有人类&#xff0c;土地利用随着人类的出现而发生。人类为了一定的社会或经济方面的目的&#xff0c;会通过利用、改造等活动。从土地上获得更多的资源。土地利用既要受自然条件的制约&#xff0c;同时也…

2023年国赛试题:配置inux1 为 CA 服务器

试题内容:配置 linux1 为 CA 服务器,为 linux 主机颁发证书。证书颁发机构有 效期 10 年,公用名为 linux1.skills.lan。申请并颁发一张供 linux 服务器使用的证书,证书信息:有效期 =5 年,公用名=skills.lan, 国家=CN,省=Beijing,城市=Beijing,组织=skills,组织单位…

Unity UGUI的自动布局-LayoutGroup(水平布局)组件

Horizontal Layout Group | Unity UI | 1.0.0 1. 什么是HorizontalLayoutGroup组件&#xff1f; HorizontalLayoutGroup是Unity UGUI中的一种布局组件&#xff0c;用于在水平方向上对子物体进行排列和布局。它可以根据一定的规则自动调整子物体的位置和大小&#xff0c;使它们…

亚马逊云科技实现了奇瑞捷豹路虎SAP系统的上云目标并保持成本优化

11月23日&#xff0c;“2023第八届IDC中国数字化转型年度盛典”正式开启并揭晓“2023 IDC中国未来企业大奖-卓越奖”获奖企业&#xff0c;奇瑞捷豹路虎汽车有限公司&#xff08;以下简称“奇瑞捷豹路虎”&#xff09;凭借“基于云原生的智慧化运营平台”项目&#xff0c;继获得…

自动驾驶HWP功能规范

HWP功能规范 Highway Pilot Functional Specification 文件状态&#xff1a; 【√】草稿 【】正式发布 【】正在修改 文件起草分工 撰写&#xff1a; 审核&#xff1a; 编制&#xff1a; 签名&#xff1a; 日期&#xff1a; 审核&#xff1a; 签名&#xff1a; 日期&am…

企业业务场景如何实现自动化连接?

为什么要实现企业业务场景的自动化连接&#xff1f; 可提高效率&#xff0c;自动化连接可以减少人工操作和手动干预的需求&#xff0c;从而提高业务处理的速度和效率。通过自动化连接&#xff0c;不同的系统、应用程序和流程可以自动协同工作&#xff0c;减少了人工处理的时间和…

【计算机组成原理】存储系统

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理计算机组成原理中 存储系统的知识点和值得注意的地方 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以…

打游戏NVIDIA怎么设置性能最好?

打游戏NVIDIA怎么设置性能最好&#xff1f;当前很多用户都在Win10电脑上畅玩游戏&#xff0c;所以想知道NVIDIA控制面板最佳设置方法&#xff0c;更好地发挥NVIDIA控制面板性能&#xff0c;用户就能享受更棒的游戏乐趣。接下来小编给大家详细介绍NVIDIA显卡游戏最佳设置步骤教程…

【Proteus仿真】【51单片机】智能晾衣架设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1604液晶、按键、蜂鸣器、L298N电机、PCF8591 ADC模块、DHT11温湿度传感器、雨滴传感器、风速、光线传感器等。 主要功能&#xff1a; 系统运行…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之Linux 进程管理 6》(10)

《Linux操作系统原理分析之Linux 进程管理 6》&#xff08;10&#xff09; 4 Linux 进程管理4.6 Linux 管道4.6.1 管道的概念4.6.2 无名管道1.终端使用2.程序中使用 4.6.2 命名管道1.终端使用2.程序中使用 4 Linux 进程管理 4.6 Linux 管道 4.6.1 管道的概念 1、管道是 linu…

2019年10月17日: Go生态洞察:在Go 1.13中处理错误

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Mysql数据库多表数据查询问题

1、背景 线上某个业务数据分表存储在10个子表中&#xff0c;现在需要快速按照条件&#xff08;比如时间范围&#xff09;筛选出所有的数据&#xff0c;主要是想做一个可视化的数据查询工具&#xff0c;给产研团队使用。 2、实践 注意&#xff1a;不要在线上真实数据库操作&am…

用户选择PowerFlex的十大理由

既有高性能      满足AI、VDI、数据库等工作负载      又有开放架构      4个节点起步可扩展至上千节点      支撑起不断变化的新兴应用负载      还能与云互通      实现云上云下互联      Dell PowerFlex      连续八季度销量增长      …