文章目录
一、架构知识基础
1.1 名词概念
耦合性:指模块之间联系的紧密程度。模块之间联系得越紧密,它们的耦合性越强,模块间的独立性就越差
内聚性:指一个模块内部各个元素之间联系的紧密程度。一个模块内的各元素之间联系得越紧密,该模块的内聚性就越高
1.2 低内聚的坏处
一个模块的内聚性很低,表现为模块的功能不单一,各功能间联系很小,甚至没有联系。这违背了单一职责原则。当类的职责过多时,它们会变得难以维护和理解,因为任何职责的变化都可能影响到其他职责。
1.3 高耦合的坏处
耦合度很高可能导致“牵一发而动全身”,模块的维护和扩展将变得很复杂。因为修改一个模块会牵连到与其关联的模块,这些模块的异常又可能影响与它们关联的模块,进一步导致恶性循环。
1.4 依赖倒置原则
是面向对象设计的六大原则之一,其核心思想:
- 高层模块不因该直接依赖低层模块,而是依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
术语解释:
- 依赖指个组件或模块需要调用另一个组件或模块(即依赖的含义等价于使用和调用)
- 高层模块和低层模块是架构设计中的一种相对概念,允许高层调用低层,不允许低层调用高层
- 抽象就是指接口或抽象类,细节就是实现类
强化理解:上层中的类A直接依赖于下层的接口B,接口B有多个实现类C和D,如此实现了上层与下层之间的解耦。当我们将接口B的实现类从类C切换到类D,或修改类B的代码时,可以减少或者避免修改上层类A中的代码。
二、MVC架构
2.1 层的划分以及各层的职责
- Model:模型层,负责处理数据和业务逻辑,包括对数据的存储、读取、验证等操作。模型是应用程序的核心组件,与视图和控制器相互独立,可以被其他应用程序重复使用
- View:视图层,负责显示用户界面,将数据呈现给用户。视图通常是根据模型中的数据生成的,可以根据需要进行定制和修改
- Controller:控制层,负责协调和管理模型和视图之间的交互,并处理用户输入和操作。控制器接收用户的请求,更新模型和视图,将结果返回给用户。控制层作为三层的中心,起着“承上启下”的作用,通过调用下层来为上层提供服务,本身不涉及任何业务操作
2.2 请求的处理流程
(配图来源:MVC与三层架构理解)
- 用户发送请求,请求由控制器接收并处理
- 控制器根据用户请求选择相应的模型和视图,并将请求传递给模型
- 模型根据请求处理数据,并将处理结果返回给控制器
- 控制器根据模型返回的数据更新视图,并将更新后的视图返回给用户
2.3 MVC架构的优点
分层的目的都是为了实现“高内聚,低耦合”。在MVC架构的三层中,任何一层的变化都不会影响到其他层。
2.4 Spring MVC的三层架构
Spring MVC针对MVC架构进行了进一步的封装,帮助开发者屏蔽底层细节,并且开放出相关接口供开发者调用,让MVC开发变得更加简单。
具体来说,Spring MVC的三层指controller层、service层和dao层。其中controller层负责路由,是Spring MVC中的表现层,相当于MVC架构中View+Controller层。Spring MVC将Model层拆分成了service层和dao层,前者负责业务逻辑,后者负责数据库的操作。
Spring MVC将Model层进一步地拆分成了service层(业务逻辑层)和dao层(数据访问层),其中service层用来实现业务逻辑,而dao层负责数据库操作。(个人理解,可能不对:将view层和controller层合并为新的controller层,作为表现层暴露接口,将model层拆分为service层和dao层)
Spring MVC中的请求处理流程:(配图来源:超详细讲解SpringMVC三层架构)
三、DDD架构(Domain Driven Design)
3.1 经典的DDD架构
(配图来源:DDD架构)
- 用户接口层(User Interface ):负责向用户显示信息和解释用户指令(是DDD架构中的表现层)(表现层是视图层的超集,概念有所区别,知道最上层就是表现层即可)
- 应用层(Application):很“薄”的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。在应用层协调多个服务和领域对象完成服务的组合和编排,协作完成业务操作。此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间服务的组合和编排
- 领域层(Domain):是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象
- 基础层(Infrastructure):贯穿所有层,为其它层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等
个体理解:将service层拆分为了应用层和领域层。其中应用层关注于用例和流程,不涉及业务规则或逻辑,通过组合和编排下层的领域层来完成业务操作。而领域层用于封装具体的业务规则或逻辑,拆分出来的领域层不再和具体流程关联,实现了高内聚和低耦合,还提高了领域层的可复用性。用户接口层和基础层则为原来的视图层和dao层的扩展,新增了部分职责功能。
3.2 改进的DDD架构(微服务中常用)
以下仅为一个示例,相较于经典的DDD架构新增了facade层和remote层。在实际的开发中,不同公司采用的项目结构可能会有所差异,但是大体结构应该是一致的。
关注点:一级层级的划分,一级层级的调用关系,二级子层的划分,二级层级的职责
3.2.1 层级划分方式和调用关系(依赖关系)
facade层级的说明:单独抽离出来的一层,专门负责对API层提供入参DTO、出参DTO和错误码
依赖倒置的体现:应用层直接依赖于领域层的接口(如repo和gateway),通过这些接口间接依赖于下层中具体的实现类(基础设施层中的repoImpl和远程服务层中的gatewayImpl)
3.2.2 细分的层级划分及其职责
├─com.company.microservice
│ │
│ ├─api 用户接口层
│ │ ├─config 全局配置
│ │ ├─controller 控制器
│ │ ├─job 任务调度
│ │ ├─mq 消息接收
│ │ └─StartMain 启动类
│ │
│ ├─facade 门面层
│ │ ├─common 入参DTO和出参DTO的通用超类
│ │ ├─errorcode 错误码
│ │ ├─request 入参DTO(相当于传统的VO)
│ │ └─response 出参DTO(相当于传统的DTO)
│ │
│ ├─application 应用层
│ │ ├─service 应用层接口服务
│ │ ├─convert 负责DTO与DO的转换
│ │ └─*** others
│ │
│ ├─domain 领域层
│ │ ├─entity 领域对象DO(相当于传统的BO)
│ │ ├─gateway 网关接口
│ │ ├─repo dao接口
│ │ ├─enums 枚举
│ │ ├─contents 常量
│ │ └─domainservice 领域层服务
│ │
│ ├─infrastructure 基础设施层
│ │ ├─mapper 持久层接口映射
│ │ ├─pojo 持久化对象PO
│ │ ├─repoimpl dao接口实现
│ │ ├─convert 负责DTO与PO的转换
│ │ └─resources/sqlmap sql资源配置
│ │
│ └─remote 远程服务层
│ │ ├─feign 内部feign接口
│ │ ├─gatewayimpl 网关接口实现
│ │ └─convert 负责DTO与DO的转换
│ │
│ └─test 单元测试层
│ │ └─***
数据库操作
接口在domain层的repo,实现在infrastructure层的repoimpl。
使用时在service层中调用repo实现数据库操作。
外部接口调用
接口在domain层的gateway,实现在remote层的gatewayimpl。
使用时在service层中调用dateway来调用外部接口。
补充:service调接口,接口与实现分离的意义
- 解耦合:通过定义清晰的接口,确保模块之间仅通过接口进行通信,而不直接依赖于彼此的具体实现。这样,当实现细节发生变化时,不会影响到使用该接口的其他模块,从而降低了系统的耦合度,提高了灵活性和可维护性。
- 复用:将常用代码封装为一个类,在集中管理的同时,还能减少代码冗余。
3.2.3 数据流示意图
3.2.4 相关POJO的理解
在改进的DDD架构中,涉及三种POJO
- DTO:表现层与服务层之间的数据传输对象,包括表现层到服务层的数据传输对象requestDTO,和服务层到表现层的数据传输对象responseDTO
- DO:领域对象,表示业务实体,即封装具体业务方法所需的数据
- PO:持久化对象,表示数据库中的表与持久层中的实体类的映射关系
图中的VO相当于本文的requestDTO,而DTO相当于本文的responseDTO。写操作中三种POJO的转化流程如下:
- 请求的数据封装为VO
- 展示层把VO转换为服务层方法所需的DTO,传送给服务层
- 服务层首把DTO转换为领域层方法所需的DO,调用DO的业务方法完成具体业务
- 服务层把DO转换为持久层方法所需的PO(可以使用ORM工具,也可以不用),调用持久层的的方法完成持久化操作
强化理解:application中的方法需要的参数类型为DTO,domain中的方法需要的参数类型为DO,而dao中的方法需要的参数类型为PO。当上层调用下层时,需要将数据转换为下层方法所需的数据类型,下层返回结果给上层时,也需返回上层所需的数据类型。