第3章:技术模块分层策略——展现层、应用层、领域层、基础设施层与跨层依赖规则

3.1 分层的核心边界与哲学

分层是模块划分最经典的策略。其核心思想是将系统按职责拆分为同心圆,每一层只依赖内层,不依赖外层。本章以最常见的四层架构(展现层 → 应用层 → 领域层 → 基础设施层)为例,剖析每层的边界、职责和依赖方向。

核心理念:依赖方向必须由外向内,绝不允许内层依赖外层。这是接口契约的基石,也是后续所有模块划分、接口定义、质量属性的来源。
层次边界定义典型组件依赖方向
展现层(Presentation)用户交互入口,处理输入/输出格式化Controller、View、DTO、Session→ 应用层
应用层(Application)业务流程编排,不包含业务规则Service、Command、Query、EventPublisher→ 领域层
领域层(Domain)核心业务逻辑与规则Entity、ValueObject、DomainService、Repository接口→ 自身(无外层依赖)
基础设施层(Infrastructure)技术实现,如数据库、消息队列、外部服务RepositoryImpl、HttpClient、CacheProvider→ 领域层接口(依赖倒置)

3.2 组件职责与信任链路

每一层都有明确的信任范围:
- 展现层信任应用层提供的用例接口,但不信任底层实现。
- 应用层信任领域层的实体和规则,但不应直接访问基础设施。
- 领域层是信任锚点,不依赖任何外部框架或数据库。
- 基础设施层通过实现领域层接口获得信任,但其实现细节对外透明。

关键模式:依赖倒置原则(DIP)使得领域层完全自由,基础设施层必须遵循领域层定义的接口契约。这是实现“领域层纯净”的必经之路。

3.3 数据流与控制流分解

以一次“创建订单”操作为例,数据流与控制流穿过各层:

  1. 展现层:接收HTTP请求,反序列化为DTO,调用应用层OrderService.createOrder()。
  2. 应用层:编排事务、校验权限,将DTO转为领域对象,调用领域层Order.create()。
  3. 领域层:执行业务规则(如验库存、算总价),持久化前通过Repository接口(在领域层定义)保存。
  4. 基础设施层:实现Repository接口,执行数据库INSERT,返回结果。

控制流严格单向向内,数据流通过接口契约双向传递,但依赖方向始终向内。

四层架构依赖方向与边界 四层架构依赖方向与边界 跨层依赖规则:只能外→内,内层绝不依赖外层 展现层(Presentation) 应用层(Application) 领域层(Domain) 基础设施层(Infrastructure) 实线:直接依赖 虚线:接口实现(依赖倒置)

3.4 选型权衡与风险点

维度权衡选项风险与控制
依赖注入机制Spring IoC vs Guice vs 手写工厂框架侵入削弱纯净度 — 通过定义接口在领域层,实现层只依赖领域接口
DTO穿透层次每层复制DTO vs 共用一个DTO共用一个DTO导致领域层暴露给展现层 — 建议每个输入端口定义专用请求/响应对象
事务边界应用层开启 vs 领域层开启领域层依赖事务会破坏纯净性 — 事务应在应用层编排
查询优化领域层聚合查询 vs 应用层直接读库领域层不应为查询性能引入实现细节 — 使用CQRS模式,查询走独立路径
常见反模式:在领域层注入JpaRepository或HttpClient。这直接打破了依赖方向,导致领域层无法独立测试、无法替换基础设施。正确做法:在领域层定义接口,在基础设施层实现,通过依赖注入在应用层组装。

3.5 落地步骤

  1. 识别核心领域:画出业务边界,确定哪些是领域实体、值对象、领域服务。
  2. 定义领域层接口:包括Repository、DomainService、Factory等,只包含业务语义。
  3. 构建应用层编排:写一个用例(如PlaceOrderService),注入领域接口,协调事务与事件。
  4. 实现展现层适配:将HTTP/GRPC/消息转化为用例调用,转换DTO。
  5. 实现基础设施层:只实现领域层定义的所有接口,读写具体技术。
  6. 建立依赖规则检查:使用ArchUnit或JDepend确保模块间依赖方向正确。

3.6 验收标准

  • 自动检查:执行依赖分析工具,确认领域层没有对基础设施层或展现层的源码依赖。
  • 单元测试:领域层核心逻辑可脱离数据库、消息队列,仅依赖内存实现(如使用MockRepository)。
  • 替换验证:将基础设施层从MyBatis替换为JPA,仅需修改基础设施模块,领域与用例完全不受影响。
  • 信任链路:任何一个外层服务调用内层接口时,必须通过接口而不是实现类。引用必须来自内层模块。
实践建议:在团队初期,优先以“包结构”模拟分层(如com.xxx.presentation/com.xxx.application/com.xxx.domain/com.xxx.infrastructure),待模块稳定后再拆为独立Maven/Gradle模块,利用编译器保证依赖方向。