第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 数据流与控制流分解
以一次“创建订单”操作为例,数据流与控制流穿过各层:
- 展现层:接收HTTP请求,反序列化为DTO,调用应用层OrderService.createOrder()。
- 应用层:编排事务、校验权限,将DTO转为领域对象,调用领域层Order.create()。
- 领域层:执行业务规则(如验库存、算总价),持久化前通过Repository接口(在领域层定义)保存。
- 基础设施层:实现Repository接口,执行数据库INSERT,返回结果。
控制流严格单向向内,数据流通过接口契约双向传递,但依赖方向始终向内。
3.4 选型权衡与风险点
| 维度 | 权衡选项 | 风险与控制 |
|---|---|---|
| 依赖注入机制 | Spring IoC vs Guice vs 手写工厂 | 框架侵入削弱纯净度 — 通过定义接口在领域层,实现层只依赖领域接口 |
| DTO穿透层次 | 每层复制DTO vs 共用一个DTO | 共用一个DTO导致领域层暴露给展现层 — 建议每个输入端口定义专用请求/响应对象 |
| 事务边界 | 应用层开启 vs 领域层开启 | 领域层依赖事务会破坏纯净性 — 事务应在应用层编排 |
| 查询优化 | 领域层聚合查询 vs 应用层直接读库 | 领域层不应为查询性能引入实现细节 — 使用CQRS模式,查询走独立路径 |
常见反模式:在领域层注入JpaRepository或HttpClient。这直接打破了依赖方向,导致领域层无法独立测试、无法替换基础设施。正确做法:在领域层定义接口,在基础设施层实现,通过依赖注入在应用层组装。
3.5 落地步骤
- 识别核心领域:画出业务边界,确定哪些是领域实体、值对象、领域服务。
- 定义领域层接口:包括Repository、DomainService、Factory等,只包含业务语义。
- 构建应用层编排:写一个用例(如PlaceOrderService),注入领域接口,协调事务与事件。
- 实现展现层适配:将HTTP/GRPC/消息转化为用例调用,转换DTO。
- 实现基础设施层:只实现领域层定义的所有接口,读写具体技术。
- 建立依赖规则检查:使用ArchUnit或JDepend确保模块间依赖方向正确。
3.6 验收标准
- 自动检查:执行依赖分析工具,确认领域层没有对基础设施层或展现层的源码依赖。
- 单元测试:领域层核心逻辑可脱离数据库、消息队列,仅依赖内存实现(如使用MockRepository)。
- 替换验证:将基础设施层从MyBatis替换为JPA,仅需修改基础设施模块,领域与用例完全不受影响。
- 信任链路:任何一个外层服务调用内层接口时,必须通过接口而不是实现类。引用必须来自内层模块。
实践建议:在团队初期,优先以“包结构”模拟分层(如com.xxx.presentation/com.xxx.application/com.xxx.domain/com.xxx.infrastructure),待模块稳定后再拆为独立Maven/Gradle模块,利用编译器保证依赖方向。