DDD学习总结


引用:
https://blog.csdn.net/weixin_39635657/article/details/111636093
https://blog.csdn.net/whos2016/article/details/103927879
https://blog.csdn.net/u011537073/article/details/114607187

DDD的介绍

最近DDD真的火出了半边天,不管是大公司还是小企业,高管还是开发人员都在聊DDD。
2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计)。
DDD 全称是 Domain-Driven Design,中文叫领域驱动设计,是一套应对复杂软件系统分析和设计的面向对象建模方法论。
它是针对面向对象分析和实际的一个扩展和延伸,用面向对象的思维来划分业务。

以前的系统分析和设计是分开的,导致需求和成品非常容易出现偏差,两者相对独立,还会导致沟通困难,DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

领域是DDD的核心。领域通过聚合结合在一起。聚合间有明显的业务边界,这些边界将领域划分为一个个边界上下文。细粒度的类就组成了领域。
将业务分散到各个领域里面,极大减少了业务与业务之间的耦合。这样就保证了DDD的系统的可维护性,可扩展性和可复用性,在处理复杂的业务逻辑的时候有很大的优势。

DDD 的发展史

相信之前或多或少一定听说过领域驱动(DDD),繁多的概念会不会让你眼花缭乱?抽象的逻辑是不是感觉缺少落地实践?可能这也是 DDD 一直没得到盛行的原因吧。

话说 1967 年有了 OOP,1982 年有了 OOAD(面向对象分析和设计),它是成熟版的 OOP,目标就是解决复杂业务场景,这个过程中逐渐形成了一个领域驱动的思潮,一转眼到 2004 年的时候,Eric Evans 发表了一篇著作 Domain-driven Design: Tackling Complexity in the Heart of Software,正式定义了领域的概念,开始了 DDD 的时代。算下来也有接近 20 年的时间了,但是,事实并不像 Eric Evans 设想的那样容易,DDD 似乎一直不温不火,没有能“风靡全球”。

2013 年,Vaughn Vernon 写了一本 Implementing Domain-Driven Design 进一步定义了 DDD 的领域方向,并且给出了很多落地指导,它让人们离 DDD 又进了一步。

同时期,随着互联网的兴起,Rod Johnson 这大哥以轻量级极简风格的 Spring Cloud 抢占了所有风头,虽然 Spring MVC 推崇的失血模式并非 OOP 的皇家血统,但是谁用关心这些呢?毕竟简化开发的成本才是硬道理。

就在我们用这张口闭口 Spring 的时候,我们意识到了一个严重的问题,我们应对复杂业务场景的时候,Spring 似乎并不能给出更合理的解决方案,于是分而治之的思想下应生了微服务,一改以往单体应用为多个子应用,一下子让人眼前一亮,于是我们没日没夜地拆分服务,加之微服务提供的注册中心、熔断、限流等解决方案,我们用得不亦乐乎。

人们在踩过诸多拆分服务的坑(拆分过细导致服务爆炸、拆分不合理导致频分重构等)之后,开始死锁原因了,到底有没有一种方法论可以指导人们更加合理地拆分服务呢?众里寻他千百度,DDD 却在灯火阑珊处,有了 DDD 的指导,加之微服务的事件,才是完美的架构

DDD的作用

代码耦合度高,难维护开发

随着项目的不断发展,我们发现在之前MVC架构下的代码会像滚雪球一样越滚越大对象与对象之间的依赖或者说耦合也会越来越深。导致以后的接口很难在维护开发。DDD恰好能够应对这种老化

统一语言,方便组内人员沟通

领域专家将和开发人员,设计人员一起创建一套适用于领域建模的通用语言。通用语言必须在全队范围之内达成一致;所有成员都使用通用语言进行交流,通用语言也是对软件模型的直接反映。请注意,虽然团队中同时包含领域专家和开发人员。设计人员,但并不是“我们”和“他们”的关系,团队中只有“我们”的概念。通用语言也有助于促使原本存在分歧的领域专家们达成一致意见。此外,通过将领域知识传达给所有的团队成员,包括开发人员,整个团队也将更具凝聚力。我们甚至可以认为,这是每个公司都应该有的对于知识型工作者的起码训练。
比如在eb-tran中有一个功能,HK的同事叫下载,但是我的理解是取数据。
又比如我们很多时候一个column或者一个实体,在很多系统间的调用传递,他们的名字都可能会不一样,这其实会造成理解上的问题(er_name,employer_name).如果是领域专家将和开发人员,设计人员建立领域模型话,我想不会出现这样的问题。

DDD的一些概念

讨论完宏观概念以后,让我们来认识一下 DDD 的一些概念吧,每个概念我都为你找了一个 Spring 模式开发的映射概念,方便你理解,但要仅仅作为理解用,不要过于依赖。

另外,这里你可能需要结合后面的代码反复结合理解,才能融汇贯通到实际工作中。

领域

映射概念:切分的服务。

领域就是范围。范围的重点是边界。领域的核心思想是将问题逐级细分来减低业务和系统的复杂度,这也是 DDD 讨论的核心。

子域

映射概念:子服务。

领域可以进一步划分成子领域,即子域。这是处理高度复杂领域的设计思想,它试图分离技术实现的复杂性。这个拆分的里面在很多架构里都有,比如 C4。

核心域

映射概念:核心服务。

在领域划分过程中,会不断划分子域,子域按重要程度会被划分成三类:核心域、通用域、支撑域。

决定产品核心竞争力的子域就是核心域,没有太多个性化诉求。

桃树的例子,有根、茎、叶、花、果、种子等六个子域,不同人理解的核心域不同,比如在果园里,核心域就是果是核心域,在公园里,核心域则是花。有时为了核心域的营养供应,还会剪掉通用域和支撑域(茎、叶等)。

通用域

映射概念:中间件服务或第三方服务。

被多个子域使用的通用功能就是通用域,没有太多企业特征,比如权限认证。

支撑域

映射概念:企业公共服务。

对于功能来讲是必须存在的,但它不对产品核心竞争力产生影响,也不包含通用功能,有企业特征,不具有通用性,比如数据代码类的数字字典系统。

统一语言

映射概念:统一概念。

定义上下文的含义。它的价值是可以解决交流障碍,不管你是 RD、PM、QA 等什么角色,让每个团队使用统一的语言(概念)来交流,甚至可读性更好的代码。

通用语言包含属于和用例场景,并且能直接反应在代码中。

可以在事件风暴(开会)中来统一语言,甚至是中英文的映射、业务与代码模型的映射等。可以使用一个表格来记录。

限界上下文

映射概念:服务职责划分的边界。

定义上下文的边界。领域模型存在边界之内。对于同一个概念,不同上下文会有不同的理解,比如商品,在销售阶段叫商品,在运输阶段就叫货品。
理论上,限界上下文的边界就是微服务的边界,因此,理解限界上下文在设计中非常重要。

聚合

映射概念:包。

聚合概念类似于你理解的包的概念,每个包里包含一类实体或者行为,它有助于分散系统复杂性,也是一种高层次的抽象,可以简化对领域模型的理解。

拆分的实体不能都放在一个服务里,这就涉及到了拆分,那么有拆分就有聚合。聚合是为了保证领域内对象之间的一致性问题。

在定义聚合的时候,应该遵守不变形约束法则:

聚合边界内必须具有哪些信息,如果没有这些信息就不能称为一个有效的聚合;

聚合内的某些对象的状态必须满足某个业务规则:

一个聚合只有一个聚合根,聚合根是可以独立存在的,聚合中其他实体或值对象依赖与聚合根。

只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性。

聚合根

映射概念:包。

一个上下文内可能包含多个聚合,每个聚合都有一个根实体,叫做聚合根,一个聚合只有一个聚合根。

实体

映射概念:Domain 或 entity。

《领域驱动设计模式、原理与实践》一书中讲到,实体是具有身份和连贯性的领域概念,可以看出,实体其实也是一种特殊的领域,这里我们需要注意两点:唯一标示(身份)、连续性。两者缺一不可。

你可以想象,文章可以是实体,作者也可以是,因为它们有 id 作为唯一标示。

值对象

映射概念:Domain 或 entity。

为了更好地展示领域模型之间的关系,制定的一个对象,本质上也是一种实体,但相对实体而言,它没有状态和身份标识,它存在的目的就是为了表示一个值,通常使用值对象来传达数量的形式来表示。

比如 money,让它具有 id 显然是不合理的,你也不可能通过 id 查询一个 money。

定义值对象要依照具体场景的区分来看,你甚至可以把 Article 中的 Author 当成一个值对象,但一定要清楚,Author 独立存在的时候是实体,或者要拿 Author 做复杂的业务逻辑,那么 Author 也会升级为聚合根。

聚合和聚合根
边界上下文
领域服务
领域
实体和值对象
工厂和仓库

四种 Domain 模式

失血模型

Domain Object 只有属性的 getter/setter 方法的纯数据类,所有的业务逻辑完全由 business object 来完成。
请添加图片描述

贫血模型

简单来说,就是 Domain Object 包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到 Service 层。请添加图片描述
注意这个模式不在 Domain 层里依赖 DAO。持久化的工作还需要在 DAO 或者 Service 中进行。

这样做的优缺点

优点:各层单向依赖,结构清晰。

缺点:

Domain Object 的部分比较紧密依赖的持久化 Domain Logic 被分离到 Service 层,显得不够 OO

Service 层过于厚重

充血模型

充血模型和第二种模型差不多,区别在于业务逻辑划分,将绝大多数业务逻辑放到 Domain 中,Service 是很薄的一层,封装少量业务逻辑,并且不和 DAO 打交道:

Service (事务封装) —> Domain Object <—> DAO 请添加图片描述
所有业务逻辑都在 Domain 中,事务管理也在 Item 中实现。这样做的优缺点如下。

优点:

更加符合 OO 的原则;

Service 层很薄,只充当 Facade 的角色,不和 DAO 打交道。

缺点:

DAO 和 Domain Object 形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。

如何划分 Service 层逻辑和 Domain 层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能 导致整个结构的混乱无序。

胀血模型

基于充血模型的第三个缺点,有同学提出,干脆取消 Service 层,只剩下 Domain Object 和 DAO 两层,在 Domain Object 的 Domain Logic 上面封装事务。
Domain Object (事务封装,业务逻辑) <—> DAO
似乎 Ruby on rails 就是这种模型,它甚至把 Domain Object 和 DAO 都合并了。

这样做的优缺点:

简化了分层

也算符合 OO

该模型缺点:

很多不是 Domain Logic 的 Service 逻辑也被强行放入 Domain Object ,引起了 Domain Object 模型的不稳定;

Domain Object 暴露给 Web 层过多的信息,可能引起意想不到的副作用。

MVC VS DDD四层架构

请添加图片描述

接口层

用户界面层,或者表现层,负责向用户显示解释用户命令

应用层

定义软件要完成的任务,并且指挥协调领域对象进行不同的操作。该层不包含业务领域知识。

领域层

或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手

基础设施层

主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现;

代码结构描述

├─com.company.microservice
│ │
│ ├─apis API接口层
│ │ ├─model 视图模型,数据模型定义 vo/dto(大多数情況是一样的)
│ │ ├─assembler 装配器,实现模型转换eg. apiModel<=> domainModel
│ │ └─controller 控制器,对外提供(Restful)接口
│ │
│ ├─application 应用层
│ │ ├─service 应用服务,非核心服务
│ │ ├─task 任务定义,协调领域模型
│ │ └─*** others
│ │
│ ├─domain 领域层
│ │ ├─common 公共代码抽取,限于领域层有效
│ │ ├─events 领域事件
│ │ ├─model 领域模型
│ │ │ ├─dict 领域划分的模块,可理解为子域划分
│ │ │ │ ├─DictVo.java 领域值对象
│ │ │ │ ├─DictEntity.java 领域实体,充血的领域模型,如本身的CRUD操作在此处
│ │ │ │ ├─DictAgg.java 领域聚合,通常表现为实体的聚合,需要有聚合根
│ │ │ │ └─DictService.java 领域服务,不能归与上述模型,如分页条件查询等可写在此处
│ │ │ ├─xxx
│ │ │ │ ├─xxxEntity.java
│ │ │ │ ├─bbbAgg.java
│ │ │ │ └─cccAgg.java
│ │ ├─service 领域服务类,一些不能归属某个具体领域模型的行为
│ │ └─factory 工厂类,负责复杂领域对象创建,封装细节
│ │
│ ├─infrastructure 基础设施层
│ │ ├─persistent 持久化机制
│ │ │ ├─po 持久化对象
│ │ │ └─repository 仓储类,持久化接口&实现,可与ORM映射框架结合
│ │ ├─general 通用技术支持,向其他层输出通用服务
│ │ │ ├─config 配置类
│ │ │ ├─toolkit 工具类
│ │ │ └─common 基础公共模块等
│ │
│ └─resources
│ ├─statics 静态资源
│ ├─template 系统页面
│ └─application.yml 全局配置文件

战术设计和战略设计

在这里插入图片描述

事件风暴寻找模型和聚合

事件风暴(Event Storming)寻找模型和聚合
领域模型( Domain Model )通过聚合( Aggregate )组织在一起,聚合间有明显的业务边界,这些边界将领域划分为一个个限界上下文( Bounded Context )。理论清楚了,问题来了,怎么来找模型和聚合呢?有一个非常流行的方法就是 Event Storming (事件风暴),它是由 Alberto Brandolini 发明的,经历了 DDD 社区和很多团队的实践,也是一种非常有参与感的团队活动。下面简单介绍一下事件风暴。
Event Storming 是一项团队活动,旨在通过领域事件识别出聚合根,进而划分微服务的限界上下文。在活动中,团队先通过头脑风暴的形式罗列出领域中所有的领域事件,整合之后形成最终的领域事件集合,然后对于每一个事件标注出导致该事件的命令( Command ),然后为每个事件标注出命令发起方的角色,命令可以是用户发起,也可以是第三方系统调用或者是定时器触发等。最后对事件进行分类,整理出聚合根以及边界上下文。事件风暴还有一个额外的好处,可以加深参与人员对领域的认识。但是需要注意的一是:在事件风暴活动中,需要有领域专家在现场进行评估,事件风暴活动的形式如图25—11所示。

Event Storming 是一项非常有创造性的活动,也是一个持续讨论和反复改进的过程,不同的团队关注的核心域( Core Domain )不同,得到的最终结果也会有差异。

简单的落地demo

CQRS

Event Sourcing

Halo or Axon

项目使用DDD面临的一些困难

  1. 现阶段没有太成熟的框架指导。大部分还停留在思想层面
  2. 需要从上到下团队思想统一,并不是每个人都认同DDD。DDD会让开发人员的工作量增加,前期也会增大项目的预算。而且需要一个长的时期才会体会到DDD的好处,老板短期不懂DDD,短期又看不到效率,可能也不会赞同。
  3. 领域划分需要业务专家,架构师,开发,产品。。。等等一起参与,role太多了,增大了复杂度

待续。。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值