分层架构中的边界:表现层、应用层、领域层、基础设施层的职责与解耦规则
分层架构是解决方案设计中最成熟、最通用的结构范式之一。但“分层”本身并不带来价值——真正带来价值的是层与层之间清晰的边界定义和严格的解耦纪律。本章聚焦四层经典分层(表现层、应用层、领域层、基础设施层),从边界识别、职责划分、数据流控制流、信任链路到落地验收,给出可操作的边界实践指南。
边界识别 — 层与层之间的割裂带
每一层都代表一个关注点维度,层间边界本质上是职责变化率和依赖方向的分界线。变化原因不同、变化频率不同、依赖方向相反的代码,就应该分属不同层级。
| 边界位置 | 边界两侧的职责差异 | 典型“越界”信号 |
|---|---|---|
| 表现层 ↔ 应用层 | 用户交互 / 用例编排 | Controller 中直接调用 Repository |
| 应用层 ↔ 领域层 | 事务与工作流 / 核心业务规则 | Service 中包含 if-else 业务判断 |
| 领域层 ↔ 基础设施层 | 业务抽象 / 技术实现 | Entity 中引用 JPA 或 Redis 注解 |
| 基础设施层 ↔ 外部系统 | 适配转换 / 原生协议 | 基础设施代码中混入业务逻辑 |
边界识别第一原则:如果一段代码同时因为“UI 变更”和“业务规则变更”而需要修改,说明它跨越了不该跨越的边界。
分层设计 — 横向切分与纵向切分
四层架构是横向切分(按技术关注点分层),而业务模块是纵向切分(按功能域分组)。两者交织形成网格结构。每一层只对直接下层有依赖,且依赖方向必须指向领域层。
上图展示了四层的垂直堆叠关系与核心解耦约束。注意:领域层不引用任何基础设施层的具体实现,它只定义接口(仓储接口、事件接口),由基础设施层实现。依赖方向永远指向领域层,这是分层架构的“宪法”。
模块职责 — 每层该做什么,不该做什么
每层的职责边界一旦模糊,架构就会退化。下表列出每层的“应做清单”和“不应做清单”:
| 层级 | 应做 (Responsibility) | 不应做 (Violation) |
|---|---|---|
| 表现层 | 请求解析、参数校验、视图渲染、Session 管理 | 调用 Repository、编写业务规则、处理事务 |
| 应用层 | 用例编排、事务控制、权限检查、DTO 转换 | 包含 if-else 业务逻辑、直接操作数据库 |
| 领域层 | 核心业务规则、实体行为、值对象不变性、领域事件 | 引用 ORM 注解、依赖 HTTP 或消息框架 |
| 基础设施层 | 数据持久化、消息发送、缓存操作、外部 API 适配 | 包含业务判断、跨层跳过应用层 |
职责检查技巧:针对任意一个类,问两个问题——(1)如果切换 UI 框架,这个类是否需要修改?(2)如果变更一条业务规则,这个类是否需要修改?如果两个答案都是“是”,说明它跨越了边界。
数据流与控制流 — 请求的完整生命周期
一次典型的请求在四层之间以“请求—响应”模式逐层传递,每层只做自己职责内的事,然后交给下一层。控制流由应用层主导,数据流以 DTO / 领域对象为载体跨层传输。
- 表现层接收 HTTP 请求,反序列化为 DTO,调用应用层服务。
- 应用层将 DTO 转换为领域对象(或直接传递),启动事务,调用领域层服务。
- 领域层执行业务规则,通过仓储接口(定义在领域层)持久化数据,发布领域事件。
- 基础设施层实现仓储接口,执行 SQL / NoSQL / 消息操作,返回结果。
- 响应沿原路径逆向返回:领域层返回结果 → 应用层组装 DTO → 表现层序列化输出。
// 伪代码:请求在各层的完整流转示意
// 表现层
@PostMapping("/orders")
public OrderResponse createOrder(@RequestBody OrderRequest request) {
CreateOrderCommand cmd = request.toCommand();
OrderResult result = orderApplicationService.createOrder(cmd);
return OrderResponse.from(result);
}
// 应用层
@Transactional
public OrderResult createOrder(CreateOrderCommand cmd) {
Order order = orderDomainService.placeOrder(cmd.toDomain());
orderRepository.save(order); // 调用领域层定义的接口
return OrderResult.from(order);
}
// 领域层
public Order placeOrder(CreateOrderCommand cmd) {
Order order = new Order(cmd.customerId, cmd.items);
order.calculateTotal(); // 核心业务规则
this.domainEvents.add(new OrderPlacedEvent(order));
return order;
}
// 基础设施层
public class OrderRepositoryImpl implements OrderRepository {
public void save(Order order) {
entityManager.persist(order); // 具体技术实现
}
}
常见反模式:应用层直接调用基础设施层的 Repository 实现类,绕过了领域层。这会导致业务规则散落在应用层,领域层退化为“数据容器”。
信任链路 — 依赖方向与接口适配
分层架构的信任模型建立在单向依赖之上:上层信任下层提供的接口契约,下层信任上层不会越过边界直接访问实现细节。领域层是信任链路的“锚点”,所有层都围绕领域层定义接口。
- 依赖倒置(DIP):领域层定义接口,基础设施层实现接口。领域层不依赖基础设施层,两者都依赖抽象。
- 接口归属:仓储接口、事件发布器接口等抽象定义在领域层,不在基础设施层定义。
- 跨层数据不可变:传递到下层的数据,下层不能修改;如需修改,必须返回新对象。
// 接口归属领域层(正确)
// 文件位置: domain/repository/OrderRepository.java
public interface OrderRepository {
void save(Order order);
Order findById(OrderId id);
}
// 基础设施层实现(正确)
// 文件位置: infrastructure/persistence/OrderRepositoryImpl.java
public class OrderRepositoryImpl implements OrderRepository {
// JPA / MyBatis / MongoDB 实现
}
选型权衡 — 分层带来的得与失
| 维度 | 优点 | 代价 |
|---|---|---|
| 可维护性 | 每层职责单一,修改范围可控 | 新增一个简单功能需要修改 4 层文件 |
| 可测试性 | 领域层可脱离基础设施做纯单元测试 | 需要大量 Mock 和接口适配代码 |
| 技术替换 | 替换 ORM 或消息队列不影响领域层 | 接口抽象增加初次开发成本 |
| 团队协作 | 前后端、业务、基础架构可以并行 | 严格的代码评审和架构守护成本 |
| 性能 | — | 多层 DTO 拷贝和调用链带来额外开销 |
选型建议:当业务复杂度超过 10 个实体且有多条跨实体规则时,四层分层的收益显著大于成本。极简单 CRUD 场景可考虑“扁平化”处理,但必须预留向分层演进的接口边界。
风险点 — 分层架构的常见陷阱
- 层间泄漏:表现层直接使用领域对象(如 Entity)渲染视图,导致 UI 变更波及领域层。应使用 DTO 隔离。
- 贫血领域模型:领域层只有 getter/setter,所有业务逻辑放在应用层。领域层退化为“数据袋”。
- 循环依赖:领域层依赖基础设施层的接口(本应相反),导致 Maven/Gradle 无法编译。
- 过度分层:为每一个简单查询都创建完整的四层结构,反而降低可读性和开发效率。
- 事务扩散:事务注解出现在应用层和基础设施层多处,难以追踪。事务边界应统一在应用层控制。
避坑口诀:表现层只认 DTO,应用层管事务,领域层守规则,基础设施层做实现;接口定义在领域,依赖方向不回头。
落地步骤 — 从零开始构建分层
- 识别核心域:与业务方一起梳理实体、值对象、聚合根,定义领域层边界。
- 先写领域层接口:定义仓储接口、事件接口、领域服务接口,不写实现。
- 再写应用层用例:每个用例对应一个应用服务方法,编排领域层调用。
- 实现基础设施层:按领域层接口编写 JPA / MyBatis / Redis 等具体实现。
- 构建表现层:编写 Controller 或 GraphQL Resolver,通过 DTO 与应用层通信。
- 配置依赖注入:将基础设施层的实现注入到接口中(如 Spring @Bean 或手动 DI)。
- 写跨层契约测试:验证请求从表现层到基础设施层的完整链路正确性。
验收标准 — 如何衡量分层是否合理
| 验收项 | 检查方式 | 通过标准 |
|---|---|---|
| 依赖方向检查 | 使用 ArchUnit 或 JDepend 分析包依赖 | 领域层不依赖任何基础设施层包 |
| 层间引用检查 | 代码审查 + 依赖分析工具 | 表现层不直接引用基础设施层类 |
| DTO 隔离检查 | 搜索 @Entity 是否出现在 Controller 中 | 表现层无领域对象引用 |
| 业务规则位置 | 统计 if-else / switch 在层中的分布 | 90% 以上业务规则在领域层 |
| 修改影响范围 | 模拟一次 UI 变更和一次业务规则变更 | 变更只涉及对应层,不跨层修改 |
| 接口归属验证 | 检查所有仓储接口定义位置 | 仓储接口 100% 定义在领域层 |
验收红线:如果在验收时发现“领域层直接使用了 MyBatis 的 @Select 注解”或“Controller 中调用了 Repository.save()”,视为架构违规,必须重构后才能通过。
分层架构的边界不是一道墙,而是一组可验证、可守护的设计契约。当每一层的职责清晰、依赖方向正确、数据流转有序时,整个解决方案就具备了应对业务变化和技术替换的核心韧性。