【笔记】DDD实战课-人保架构欧创新

开篇

学好DDD,你能做什么?

DDD是一种思想,可以指导中台设计和微服务的拆分。

应用DDD的前提:
1、要吃透 DDD 的核心设计思想,
2、搞清楚 DDD、微服务和中台之间的关系。

三角关系:
把DDD->业务中台->微服务这个过程可以粗略类比成:思想->业务模型->系统落地。

一些概念:
基础:
领域、子域、核心域、通用域、支撑域、限界上下文、实体、值对象、聚合和聚合根等概念。

进阶:
领域事件DDD 分层架构、几种常见的微服务架构模型以及中台设计思想等内容。
通过领域事件实现微服务解耦?
怎样进行微服务分层设计?
如何实现层与层之间的服务协作?
几种微服务架构模型的对比分。
如何利用 DDD 进行中台设计?如何实现前中后台的协同和融合?

实战:
中台和领域建模的实战:
如何用 DDD 设计思想构建企业级可复用的中台业务模
型。
了解事件风暴以及用事件风暴构建领域模型的过程。
微服务设计实战:
如何用 DDD 设计微服务代码模型,
如何从领域模型完成微服务设计,
建立领域模型与微服务代码模型的映射关系,
如何完成微服务的架构演进等。

微服务的设计思想来设计前端应用:
实现前端应用的解耦。

基础

领域驱动设计:微服务设计为什么要选择 DDD?

微服务架构可以实现应用之间的解耦,解决单体应用扩展性和弹性伸缩能力不足的问题。
微服务如何解决团队的敏捷开发:个人认为从开发效率,单元测试效率,构建效率都有提升。其实秘密就在于把大的单体应用拆小,实现船小“好调头”的效果。

从某种意义上也是微服务应用的出现促进了DDD的思想的传播。

DDD的两层设计

战略设计:粗粒度,个人理解更靠近业务侧,包括建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
战术设计: 细粒度,个人理解更靠近技术侧,包括聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

设计过程:
通过事件风暴的方式,从发散到收敛。
发散:拆分分解,用例分析、场景分析和用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,产生的结果是实体、命令、事件等领域对象。
收敛:聚类,按不同维度分类,形成如聚合、限界上下文等边界,建立领域模型的过程。
以上过程从微观到宏观会出来两个边界:聚合的边界(虚线边界,包括实体,值对象,聚合根),限界上下文的边界(实线边界,包括多个聚合)。

DDD与微服务的关系

DDD是一种应用架构设计方法,微服务是一种应用架构风格。
本质上,追求“高响应力” 而降低系统复杂度的手段。
核心要义,是追求演进

演进式架构的含义:
业务是变化和向前发展的(虽然有人知道的多,有人知道的少,不是一开始所有人能知道全部的业务,比如系统所在行业的市场和外部环境也会影响系统需求),调整应用架构划分,优化代码,保持架构和代码的生命力(与时俱进)。

DDD 主要关注:
1、进行业务抽象建立领域模型
2、从业务视角划分领域边界
3、构建通用语言进行高效沟通
4、指导业务代码与业务保持逻辑一致

微服务主要关注:
1、独立开发、测试、构建和部署
2、运行时的进程间通信、容错和故障隔离
3、去中心化数据管理和去中心化服务治理

领域、子域、核心域、通用域和支撑域:傻傻分不清?

领域和子域

从宏观到微观的过程,子域是还可以继续分为子域的。

核心域、通用域和支撑域

核心域:决定产品和公司核心竞争力的子域,它是业务成功的主要因素和公司的核心竞争力。
通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域。举例:认证、权限。
支撑域:它是必须的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。具有企业特性,不具备通用性。

域的划分取决于特定公司的商业模式和战略方向。
同样的行业不同的客户群体和商业模式,域的划分也不一样。

限界上下文:定义领域边界的利器

在 DDD 领域建模和系统建设过程的参与者:领域专家、产品经理、项目经理、架构师、开发经理和测试经理。

通用语言

定义:在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言。
作用:高效沟通,务需求的正确表达,代码命名,代码可读性,可维护性。

通用语言如何贯穿于软件的生命周期中:
从事件风暴(团队统一语言的过程)-->领域故事分析-->提取领域对象-->映射(领域对象--通用名词术语--代码命名)-->代码落地。

表格是记录事件风暴和微服务设计过程中产生的领域对象及其属性的很好的方式。

用处

具有封装功能,对某些业务的封装。
并且保证上下文边界内部业务含义的唯一性,确切性。
举例:电商商品,在购买环节与物流环节称呼不同,购买时称为商品,物流时成为货物。

限界上下文和微服务

理论上限界上下文就是微服务的边界。
限界上下文是微服务设计和拆分的主要依据:实际中还受其它因素影响,比如技术异构、团队沟通。

实体和值对象:从领域模型的基础单元看系统设计

实体

实体的特点:
1、拥有唯一标识符,并且标识符历经各种状态变更后仍能保持一致
2、对这些对象重要的是其延续性和标识,而不是其附属的其他属性

实体的形态:
1、业务形态
实体是领域模型重要的组成部分。
是多个属性、操作或行为的载体。
实体与事件风暴:命令、操作或者事件由实体产生。
实体可以聚合。
实体和值对象是组成领域模型的基本单元。
2、代码形态
实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。
实体产生的业务逻辑(实体业务逻辑)被包含在实体方法中。
领域业务逻辑:被包含在领域服务中。
3、运行形态
ID不变,属性可以发生很大变化。
4、数据库形态
设计优先 :传统数据模型优先 VS 领域模型优先
数据模型优先:先设计数据模型
领域模型优先:针对实际业务场景构建实体对象和行为,然后再将实体->数据持久化对象。
实体与持久化对象数量关系:一对0:折扣实体对应价格配置数据;一对多:权限实体对应用户和角色两个持久化对象;多对一:客户实体和账号实体对应一个表(避免联表查询,方便查询)

值对象

值对象是一个属性的集合,这些属性可以归为一个整体,并且不可修改。
可以避免属性的零碎化。

值对象的形态:
1、业务形态
来源于事件风暴。
包含了若干个属性。
可以与实体一起构成聚合。
值对象与实体相比:业务属性比实体要少,只有数据初始化操作,以及有限的不涉及修改数据的行为,基本不包含业务逻辑。
值对象一版不会独立与实体存在,被实体包含。

2、代码形态
值对象可以是单个属性,也可以是一个“类”。

3、运行形态
除了初始化和整体替换没有其他运行时改变。

4、数据库形态
相关的值对象是否存一张表里呢?
结论:可以把值对象嵌入实体表中。
优先发挥DDD实体的威力,对数据库设计原则进行妥协,因为优先保障业务的运行才是最重要的。

实体和值对象的关系

相同:微服务基础的对象以实线基本的领域逻辑。
某些场景可以互换。

领域设计优先 VS 数据建模优先?
数据建模缺点:实体太多时,会陷入表的无穷无尽的设计,领域模型容易被数据模型绑架。
值对象可以弥补实体的缺陷。

同一个对象在不同的业务形态下可以设计不同的结果:
用户的地址在某些领域内不能修改,而作为收货地址时是可以修改的。

聚合和聚合根:怎样设计聚合?

事件风暴-> 业务操作、业务行为 -> 实体、值对象 -> 组合实体、值对象:根据业务关联进行组合 -> 形成聚合 -> 把聚合划定到限界上下文 -> 完成领域建模。

聚合

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

一个聚合的组成:
聚合根(也是一个实体)
其它被聚合的实体和值对象
上下文边界,业务单一的职责和高内聚原则确定边界。

聚合的作用:
1、 协同实体和值对象:领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合
2、保持实体之间数据一致:它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。

聚合在DDD中的位置:
属于领域层,领域层有很多个聚合。
从小到大依次为:
单个实体(个体业务能力) --> 聚合多个实体(领域服务) --> 多个聚合(应用服务)

聚合根

聚合根也称为根实体,也是实体,还是聚合的管理者。
目标:定义统一的业务规则控制,解决聚合、实体之间数据不一致性问题。
作用:对外提供聚合的接口,接收外部的任务和请求。聚合根作为一个“导航入口”可以访问聚合内部的实体,桥接外部和内部实体的桥梁。
传统的方式:没有聚合根,程序可以无控制的修改和调用实体;常常加锁去处理业务逻辑(降低系统性能)。

怎样设计聚合?

第一步:事件风暴,梳理业务行为,找出所有实体值对象
第二步:挑选聚合根(从实体中挑选)。挑选聚合根原则:有独立的生命周期,可以修改创建其他对象,有专门的模块管理这个实体。
第三步:找出聚合:一个聚合根,多个实体和值对象的对象集合。
第四步:画出依赖引用关系:实体之间的引用,聚合之间的引用
第五步:限界上下文划分:多个相关性比较强的业务语义和上下文一起

设计原则

1、聚合内不变的业务规则:
2、遵循小聚合原则
3、关联外部聚合不用对象引用,而是用唯一标识:减少耦合,边界清晰
4、聚合内强一致性,聚合之间最终一致性:一个事务对应一个聚合,多个聚合更改使用领域事件异步的方式修改。

总结

聚合的特点:
高内聚、低耦合。
拆分微服务的最小单位。
一个微服务可以包含多个聚合,在微服务架构演进时就以聚合为单位进行拆分和组合。

聚合根的特点:
聚合根是实体,有实体的特点,具有全局唯一标识。
一个聚合只有一个聚合根。
直接对象引用的方式。

实体的特点:
有 ID 标识,ID 在聚合内唯一。
依附于聚合根,其生命周期由聚合根管理。
实体可以引用聚合内的聚合根、实体和值对象。

值对象的特点:
无 ID,不可变,无生命周期,用完即扔。
核心本质是值,是一组概念完整的属性组成的集合。
值对象尽量只引用值对象。

进阶篇

领域事件:解耦微服务的关键

领域事件

广义事件分类:命令、操作和事件(事件发生会导致业务操作)。

事件对应到一个步骤:
举例:
1、缴费完成事件 触发 投保单转保单动作
2、批处理定时任务事件 触发 批量生成季缴保费通知单
3、密码连续输错三次操作事件 触发 锁定账户业务发生

如何识别领域事件

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

领域事件的数据一致性

通常使用最终一致性,而不是直接调用。原因有下:
1、聚合数据一致性设计原则:在边界之外使用最终一致性,一次数据事务只能更改一个聚合的状态。
2、领域事件与微服务之间的解耦协同要求。

根据领域事件所处位置分类:
1、同一个微服务聚合之间
流程:构建领域事件(A聚合) -> 持久化领域事件(A聚合) -> 将领域事件发布到事件总线(A聚合) -> 接收领域事件(B聚合)。

可以引入以下中间件解耦不同聚合:微服务内的事件总线,入消息中间件。
多个聚合数据一致性和事务应用: 多个聚合在进程内一个事务、多个聚合在进程内多个事务(应用分布式事务,一个聚合一个事务)、补偿手段(使用事件总线或者消息中间件的情况)

2、微服务之间
通信方式可以是直接调用:实时性好,弊端是如果引入分布式事务机制会影响系统性能,增加微服务之间的耦合。
跨微服务要考虑方面比较多:事件构建、发布和订阅、事件数据持久化、消息中间件,持久化时还可能需要考虑引入分布式事务机制等。

领域事件总体架构

1、 事件构建和发布
事件基本属性包括:事件唯一标识(全局唯一、无歧义)、发生时间、事件类型和事件源。
事件业务属性:业务数据
事件实体: 包括基本属性和业务属性

2、事件数据持久化
事件发布之前需要构建事件并且持久化。
持久化的好处:系统对账,审计,系统故障后可恢复。
根据是否与业务库在一个库里分成两种方案:本地事务或者跨数据库事务(不好保证强一致性,分布式事务会带来额外开销)。

3、事件总线 (EventBus)
负责同一个微服务内聚合之间领域事件的分发和接收,事件总线是进程内模型。
过程:订阅者是微服务内的其他聚合,直接发送到订阅者;微服务外的订阅者:持久化事件,并且发送到消息中间件。

4、消息中间件
中间件产品包括Kafka或者RabbitMQ。

5、事件接收和处理
订阅者采用监听:也要持久化,持久化后处理具体业务。

总结

领域事件的直接作用是降低了微服务之间直接访问“堆积”的压力。

领域事件实现了微服务之间的解耦(订阅方不直接依赖于发布方,可以灵活的增加订阅方),直接调用从代码层面很难做到。

07 | DDD分层架构:有效降低层与层之间的依赖

微服务架构模型的目标之一:软件架构设计的高内聚低耦合。
常见架构:整洁架构、CQRS 和六边形架构。

传统四层架构:基础层被依赖,基础层是核心。
上领域层才是软件的核心:

复习依赖倒置(Dependency inversion principle,DIP)的设计:高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。

优化后的基础层依赖用户接口层和应用层,有点不可思议。
让我们来一起看下这四个层。

几点思考:
1、Spring MVC上加参数校验合理吗?
在领域涉及里,Spring MVC只是一个基础层。
所以领域层必须有业务规则校验能力,从领域涉及的角度,这样做不方便以后代码的迁移。

2、实体与表是一一对应吗?
不是,实体是领域内的类。表是基础层内的类。
基础层也应该有自己的抽象,当需要基础层的能力时,应该以接口的方式定义出来。

posted on 2022-08-17 13:01  千里小马  阅读(712)  评论(0编辑  收藏  举报

导航