1. 框架分层模型:层职责、层间接口、层依赖原则、层边界示例
本章是《Framework层设计模式应用》的基石。你将理解一个可维护、可测试的框架为何必须采用分层架构,以及每一层承担什么职责、层与层之间如何通信、依赖关系如何被严格管控。分层不是教条,而是应对框架复杂度最有效的结构化手段。错误的边界会放大变更成本,而清晰的分层则让框架在长期演进中保持弹性。
核心原则:每一层只做“本层该做的事”,从不越俎代庖。依赖必须是单向的——外层依赖内层,内层绝不反向依赖外层。这是框架抵御腐化的最后一道防线。
1.1 分层结构总览
一个通过实践验证的成熟框架,通常划分为四个逻辑层:表现层(Presentation Layer)、应用层(Application Layer)、领域层(Domain Layer)、基础层(Infrastructure Layer)。每一层都封装了一组紧密相关的职责,并通过定义良好的接口向上层提供能力。
1.2 层职责详解
| 层名 | 核心职责 | 不应该做 | 典型组件 |
|---|---|---|---|
| 表现层 | 渲染 UI、捕获用户输入、将用户意图转化为应用命令 | 不应包含业务逻辑、数据校验之外的验证、直接访问数据库 | Controller、Presenter、View、ViewModel |
| 应用层 | 编排用例流程、进行事务管理、组织数据转换 | 不应包含领域规则、持久化逻辑、UI 渲染 | ApplicationService、UseCase、DTO Assembler |
| 领域层 | 封装业务规则、维护实体状态变化、定义领域事件 | 不应依赖框架特性、不应该直接操作数据库、不导入基础设施包 | Entity、ValueObject、Aggregate、DomainService、Repository 接口 |
| 基础层 | 提供技术细节实现:持久化、消息、网络、缓存 | 不应包含业务规则、不应直接暴露实现细节给上层、不触发领域逻辑 | RepositoryImpl、MessageBus、FileWriter、HttpClient |
1.3 层间接口与依赖方向
分层架构中最重要的原则是依赖倒置:高层模块不依赖低层模块的具体实现,二者都依赖抽象。换句话说,领域层定义仓库(Repository)接口,基础层实现该接口。层间接口应遵循以下规范:
- 接口归属:接口定义在“被调用方”所在层。
- 参数与返回值:使用本层或更内层的类型,不引入外层类型。
- 粒度:接口方法应当完整表达一个业务意图,而非暴露一条 SQL。
- 异常:只抛出本层定义的异常,不抛技术栈异常。
设计提示:一个常见的错误是将表现层直接调用基础层的 DAO 接口。这种“捷径”会瞬间打破分层约束,导致业务逻辑散落在 UI 代码中。正确的做法是:表现层 → 应用层(用例)→ 领域层(接口)→ 基础层(实现)。
1.4 层边界示例
下面通过一个“用户注册”流程展示层边界如何工作。注意各层如何通过抽象协作,且不越界调用。
// 领域层:定义实体与接口
class User {
private UserId userId;
private Email email;
// 仅持有业务数据与领域行为
}
interface UserRepository {
User save(User user);
Optional<User> findByEmail(Email email);
}
// 应用层:编排用例
class RegisterUserUseCase {
private UserRepository userRepo;
private NotificationService notifyService;
public User execute(RegisterUserCommand cmd) {
// 调用领域层接口检查重复
if (userRepo.findByEmail(cmd.getEmail()).isPresent()) {
throw new DuplicateEmailException();
}
User user = new User(UserId.generate(), new Email(cmd.getEmail()));
userRepo.save(user);
notifyService.sendWelcome(user);
return user;
}
}
// 基础层:实现领域接口
class JpaUserRepository implements UserRepository {
@Override
public User save(User user) {
// 调用 JPA API 持久化
return entityManager.merge(user);
}
}
1.5 数据流、控制流与信任链路
| 流类型 | 方向 | 信任假设 | 安全边界 |
|---|---|---|---|
| 数据流 | 表现层 → 应用层 → 领域层 → 基础层 → DB/外部系统 | 数据在进入领域层前已完成格式校验和转义 | 领域层从不信任外部输入,实体构造函数中执行不变式检查 |
| 控制流 | 表现层发起命令 → 应用层协调 → 领域层运算状态 → 基础层持久化 | 每一层只调用下一层的公开接口,不绕过 | 应用层只依赖接口,不依赖具体实现,方便在测试中替换为 Mock |
| 信任链路 | 基础层是“不信任层”,领域层是“信任决策层” | 领域层不信任基础层的返回格式,必须在聚合内完成反序列化验证 | 领域事件在领域层发布,应用层订阅,基础层负责基础设施投递 |
1.6 选型权衡与风险点
- 权衡:严格分层 vs 宽松分层。严格分层增加了跳转链路,但保证了长期可维护性。对于简单 CRUD,可以允许应用层直接调用基础层,但必须设定边界:一旦引入领域逻辑,立即回归严格分层。
- 风险点:层间逻辑泄露。最常见的是“万能层”——表现层直接塞 SQL 片段、领域层依赖 ORM Session、应用层内嵌 IO 操作。每次代码审查应重点检查 import 语句是否越界。
- 风险点:环形依赖。当基础层开始依赖领域层(比如领域层定义了一个接口,基础层实现后又被领域层反向 import),就会形成环。解决方法:将接口提取到独立模块(SharedKernel),双方都依赖它。
- 风险点:过度抽象。每一层都定义接口虽然符合依赖倒置,但过多的接口增加阅读成本。建议:只对将来可能替换或需要测试 Mock 的组件定义接口,否则直接使用类。
重要警示:切勿让“通用”或“灵活”成为破坏分层的理由。任何跨层直接调用最终都会导致架构腐烂。如果发现某一处必须“破戒”,请自觉承担三层评审义务,并在异常位置留下明确注释。
1.7 落地步骤
- 识别核心层:从领域层开始,定义实体、值对象和聚合。这一阶段完全不涉及数据库和 UI。
- 定义层间接口:为每个领域实体定义仓库接口,放在领域层内部。
- 实现应用层:用用例类包装领域操作,暴露给表现层。
- 实现基础层:创建 JPA/MyBatis 仓库实现,或消息队列实现。
- 建立模块边界:用 IDE 的模块依赖检查(如 IntelliJ 模块依赖图)或构建工具(如 Gradle 的 module-info)强制禁止跨层依赖。例如,针对 Gradle 项目设置:
// presentation/build.gradle.kts
dependencies {
implementation(project(":application"))
// 禁止直接依赖 infrastructure 或 domain
}
// application/build.gradle.kts
dependencies {
implementation(project(":domain"))
// 不依赖 presentation 或 infrastructure
}
// infrastructure/build.gradle.kts
dependencies {
implementation(project(":domain"))
}
- 自动化边界检查:使用 ArchUnit(Java)或类似的架构测试框架,在 CI 中自动验证层依赖规则不被违反。
// 使用 ArchUnit 强制层依赖规则(Java 示例)
@Test
void enforceLayerArchitecture() {
layeredArchitecture()
.layer("Presentation").definedBy("..presentation..")
.layer("Application").definedBy("..application..")
.layer("Domain").definedBy("..domain..")
.layer("Infrastructure").definedBy("..infrastructure..")
.whereLayer("Presentation").mayOnlyBeAccessedByLayers("Application")
.whereLayer("Application").mayOnlyBeAccessedByLayers("Presentation")
.whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Infrastructure")
.whereLayer("Infrastructure").mayNotBeAccessedByLayers("Domain")
.check(classes);
}
1.8 验收标准
| 编号 | 验收项 | 验收方法 |
|---|---|---|
| AC-1 | 领域层不包含任何依赖框架或数据库驱动的 import 语句 | 代码审查 + 构建依赖检查 |
| AC-2 | 表现层不直接引用基础层类,所有数据通过应用层 DTO 传递 | 静态检测 + 架构测试 |
| AC-3 | 应用层不包含领域规则逻辑,领域规则全部封装在领域实体或领域服务中 | 代码审查 |
| AC-4 | 基础层只实现领域层定义的接口,不定义自己的业务接口 | 架构测试(检查基础设施模块的 public 接口是否被领域层接口覆盖) |
| AC-5 | 层间接口参数使用领域层类型,不暴露基础层数据模型 | 代码审查 + 接口契约文档 |
| AC-6 | CI 中架构测试通过率 100%,无任何分层违规 | CI 构建日志 |
到此,你已经掌握了框架分层模型的本质。每当你设计新的模块或重构既有代码时,请以本章的层职责表为标尺,用依赖倒置和单向依赖原则来判断每一行代码的归属。下一步,我们将深入表现层的设计模式,学习如何用 MVP、MVVM 和 MVI 等模式将 UI 与业务彻底分离。