学习架构模型之前,我们先来思考一下架构能给我带来什么,提升什么?
维度 | 问题or疑问 | 痛点 |
---|---|---|
业务角度 | 1、能给业务带来什么?2、能快速支持后续业务的迭代以及优化吗?3、会不会增加研发成本? | 1、一加功能排期一个月2、换人也不行呀,没人能懂呀 |
研发角度 | 1、学习成本大不大?2、研发效率是否能提升?3、是否符合高内聚低耦合?4、扩展性高不高? | 1、每天就CRUD,重复造轮子2、研发效率低,耦合性代码太多,不敢改呀3、一个注释没有,完全看不懂,只能自己重写4、不用的代码能不能删了 屎一样的代码你都不用还有谁会用5、瞎几把写开关 瞎鸡儿特殊处理6、一段代码copy来copy去,改的时候丢三落四,该不全 |
交付质量 | 1、交付质量高不高?2、代码可读性如何? | 1、代码完全看不懂2、sb写的xxx,sm操作,sm鬼玩意3、一改就有问题,还要给前任背锅 |
因此一个好的框架既能给业务赋能,又能提高我们研发效率以及成本控制。
框架目标:
- 拒绝泥球小单体、拒绝污染功能与服务、拒绝一加功能排期一个月 --- 控制成本
- 架构出高可用极易符合互联网高速迭代的应用服务 --- 清晰结构
- 物料化、组装化、可编排的服务,提高人效 --- 扩展性强、研发效率高
一、初识DDD架构
名称:DDD(domain-driven design) 领域驱动设计
定位:一种架构设计思想
领域划分:核心子域,通用子域,支撑子域
学习目标:
1、 与MVC对比的云泥之处?
2、DDD设计原则是多少?
3、它适合什么场景?
二、架构对比
量化维度 | MVC | DDD |
---|---|---|
分层结构 | 三层结构 | 四层结构 |
模型 | 贫血模型 | 充血模型 |
开发成本 | 前期易构建 中期较难扩展后期复杂化扩展 | 前期构建花费时间中期扩展简单后期支持扩展 |
学习成本 | 易上手 | 需要领域建模经验 |
使用场景 | 项目周期短,业务复杂性低 | 项目周期长,业务复杂性高,后续迭代要求高 |
2.1、分层结构
MVC:3层架构
表现层:web 层,web 需要接收 http 请求,完成 http 响应
业务层:负责业务逻辑处理
持久层: dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行访问
DDD:4层架构
接口层:用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将信息传递给应用层。
应用层:用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装。
领域层:为完成领域中跨实体或值对象的操作转换而封装的服务,领域服务以与实体和值对象相同的方式参与实施过程。
基础层:为各层提供资源服务(如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务逻辑的影响。
2.2、模型
MVC:
贫血模型--- "状态"(数据,成员对象)、"行为"(逻辑、过程),分离到不同的对象中,只有状态的对象(VO -> Value Object) 被称为贫血模型,只有行为的对象,就是框架分层中常见的Logic/Service/Manager层(对应到EJB2中的Stateless Session Bean)
DDD:
充血模型---一个对象拥有行为和数据,在领域层包括了:对象、聚合对象、仓储和Service实现
2.3、开发成本
MVC一个面向库表编程,一开始设计好表,直接生成CRUD就可以用,因此前期成本低。而DDD是面向领域能力编程,一开始就需要建立各个业务的领域或者子领域,规划出每个领域的能力,因此前期成本高,需要站在更高的角度去思考未来业务的走向。但是随着后续的不断迭代和业务的优化,DDD的研发效率就上来。举个例子:
MVC 好比就是房子,大平层的那种,只有外面的一层的窗户和门。
DDD 好比就是好三室一厅的格局,每个房间都有自己的主人(领域)。
从MVC到DDD 只是骨架变了,但骨架之下的血肉并没有改变。如果你仍是把原有的烂代码平移到新的分层架构中,就相当于把老房子里的破旧家具衣物鞋帽搬过来而已。所以依照与软件设计的原则;分治、抽象和知识,中的知识是设计原则和设计模式的运用。所以要想把代码写好,就一定是要把DDD + 设计模式
MVC 工程的腐化根本,就在于对象、服务、组件的交叉混乱使用。时间越长,腐化的越严重。
在 MVC 的分层结构就像家里所有人的衣服放一个大衣柜、所有人的裤子放一个大库柜。衣服裤子(对象),很少的时候很节省空间,因为你的裤子别人可能也拿去穿,复用一下开发速度很快。但时间一长,就越来越乱了。🤨 一条裤子被加肥加大,所有人都穿。
而 DDD 架构的模型分层,则是以人为视角,一个人就是一个领域,一个领域内包括他所需的衣服、裤子、袜子、鞋子。虽然刚开始有点浪费空间,但随着软件的长周期发展,后续的维护成本就会降低。
2.4、学习成本
DDD领域的理念的需要有多项目经验才能体会这种方式带来的好处,还有一点就是需要我们经常挂在嘴边,一用又想不起来的设计模式,加入到DDD领域中,利用抽象、分治的设计原则去设计。总结起来就是:
1、项目经验
2、设计模型思想的熟练程度
三、DDD架构模型
3.1、DDD四层分层结构:
- 接口层
-
- 接口服务位于用户接口层,用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将信息传递给应用层。
- 应用层
-
- 应用服务位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装。
- 应用层的服务包括应用服务和领域事件相关服务。
- 应用服务可对微服务内的领域服务以及微服务外的应用服务进行组合和编排,或者对基础层如文件、缓存等数据直接操作形成应用服务,对外提供粗粒度的服务。
- 领域事件服务包括两类:领域事件的发布和订阅。通过事件总线和消息队列实现异步数据传输,实现微服务之间的解耦。
- 领域层
-
- 领域服务位于领域层,为完成领域中跨实体或值对象的操作转换而封装的服务,领域服务以与实体和值对象相同的方式参与实施过程。
- 领域服务对同一个实体的一个或多个方法进行组合和封装,或对多个不同实体的操作进行组合或编排,对外暴露成领域服务。领域服务封装了核心的业务逻辑。实体自身的行为在实体类内部实现,向上封装成领域服务暴露。
- 为隐藏领域层的业务逻辑实现,所有领域方法和服务等均须通过领域服务对外暴露。
- 为实现微服务内聚合之间的解耦,原则上禁止跨聚合的领域服务调用和跨聚合的数据相互关联。
- 基础层
-
- 基础服务位于基础层。为各层提供资源服务(如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务逻辑的影响。
- 基础服务主要为仓储服务,通过依赖反转的方式为各层提供基础资源服务,领域服务和应用服务调用仓储服务接口,利用仓储实现持久化数据对象或直接访问基础资源。
3.2、DDD设计原则
首先 DDD 的设计分为战略和战术;
- 战略设计(领域模型): 从业务视角出发,建立业务领域模型、划分职责边界,建立通用语言的界限上下文。顶层战略设计构建的领域模型结构,是整个服务后期编排的重点,它确定了功能的职责边界、聚合、对象等,也就决定了后期服务战术实现的开发和交付质量。重视战略,才能落地好战术!
- 战术设计(设计模式): 从技术视角出发,侧重于领域模型的技术实现,完成功能开发和交付落地。领域设计的重点包括:实体、聚合对象、值对象、领域服务、仓储,还有一个非常重点的设计模式。任何一个较为复杂的领域模型实现都需要考虑设计模式的使用,否则即使战略优秀,战术也能干回 MVC 去。
在以DDD领域驱动设计落地的过程中,要依靠领域驱动设计的设计思想,通过事件风暴建立领域模型,合理划分领域逻辑和物理边界,建立领域对象及服务矩阵和服务架构图,定义符合DDD分层架构思想的代码结构模型,保证业务模型与代码模型的一致性。通过上述设计思想、方法和过程,指导团队按照DDD设计思想完成微服务设计和开发。
- 拒绝泥球小单体、拒绝污染功能与服务、拒绝加功能排期一个月
- 架构出高可用极易符合互联网高速迭代的应用服务
- 物料化、组装化、可编排的服务,提高人效
- 要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计
- 要职能清晰的分层,而不是什么都放的大箩筐
DDD 的领域模型设计,界限内的上下文,可以拆分为独立的微服务。但不仅要从业务视角看问题,也要考虑非业务的技术因素,包括:高性能、安全、团队、技术异构等,这些非业务的技术因素,也会决定领域模型落地的具体落地。
四、DDD架构模版
- 应用封装 - app:这是应用启动和配置的一层,如一些 aop 切面或者 config 配置,以及打包镜像都是在这一层处理。你可以把它理解为专门为了启动服务而存在的。
- 接口定义 - api:因为微服务中引用的 RPC 需要对外提供接口的描述信息,也就是调用方在使用的时候,需要引入 Jar 包,让调用方好能依赖接口的定义做代理。
- 领域封装 - trigger:触发器层,一般也被叫做 adapter 适配器层。用于提供接口实现、消息接收、任务执行等。所以对于这样的操作,这里把它叫做触发器层。
- 领域编排【可选】 - case:领域编排层,一般对于较大且复杂的的项目,为了更好的防腐和提供通用的服务,一般会添加 case/application 层,用于对 domain 领域的逻辑进行封装组合处理。但对于一些小项目来说,完全可以去掉这一层。少量一层对象转换,代码的维护成本会降低很多。
- 领域封装 - domain:领域模型服务,是一个非常重要的模块。无论怎么做DDD的分层架构,domain 都是肯定存在的。在一层中会有一个个细分的领域服务,在每个服务包中会有【模型、仓库、服务】这样3部分。
- 仓储服务 - infrastructure:基础层依赖于 domain 领域层,因为在 domain 层定义了仓储接口需要在基础层实现。这是依赖倒置的一种设计方式。所有的仓储、接口、事件消息,都可以通过依赖倒置的方式进行调用。
- 类型定义 - gateway:对于外部接口的调用,也可以从基础设施层分离一个专门的 gateway 网关层,来封装外部 RPC/HTTP 等类型接口的调用。
- 类型定义 - types:通用类型定义层,在我们的系统开发中,会有很多类型的定义,包括;基本的 Response、Constants 和枚举。它会被其他的层进行引用使用。(这一层没有画到图中)
从APP层、触发器层、应用层,这三块主要对领域层的上下文逻辑封装、触发式(MQ、HTTP、JOB)使用,并最终在应用层中打包发布上线。这一部分的都是使用的处理,所以也不会有太复杂的操作。
当进入领域层开始,也是智力集中体现的开始了。所有你对工程的抽象能力,都在这一块区域体现。