4. 分层架构中的边界策略:展示层、应用层、领域层、基础设施层的职责与依赖方向

分层架构(Layered Architecture)是软件系统中最经典、最广泛应用的架构模式之一。在功能边界划分的实践中,分层架构提供了一套严格的“物理隔离”规范:每一层有明确的职责边界,依赖方向必须是单向的。任何跨越边界的调用都必须遵守“上层依赖下层”的铁律,禁止下层反向依赖上层或同层之间的横向穿透。

本章将深入剖析展示层、应用层、领域层、基础设施层的职责边界,以及它们之间的依赖方向、信任链路和数据流动方式。理解这些策略是后续所有边界识别工作的基础。

核心原则:依赖倒置原则(DIP)是分层边界正确的基石。高层模块(领域层)不应依赖低层模块(基础设施层),两者都应依赖抽象接口。

4.1 常见误区与破局

许多团队在实施分层架构时,往往陷入“伪分层”的困境,导致边界模糊、耦合严重。下表列出了三种最常见的错误分层实践及其后果:

误区类型 错误表现 后果
贫血模型 领域层只包含数据(Entity/POJO),所有业务逻辑放在应用层或服务层 领域层退化,业务规则散落,变更影响大
基础设施泄漏 领域层直接使用ORM注解、数据库连接、HttpRequest对象 领域层无法独立测试,更换数据库或框架代价高
跨层穿透 展示层直接调用基础设施层的Repository或DAO 跳过业务规则,安全与一致性失控

诊断方法:如果在代码审查中发现 @Entity 注解出现在领域实体上,或者领域服务方法中出现了 Connection 参数,说明边界已经被打破。

4.2 四层职责与依赖方向定义

我们采用标准的四层架构模型,每层的职责边界如下:

  1. 展示层(Presentation): 用户界面、API端点、请求/响应处理。职责仅限于接收输入、格式化输出、路由分发。不包含任何业务逻辑。
  2. 应用层(Application): 用例编排、事务控制、权限验证、事件发布。作为领域层和展示层之间的“薄胶水”,不包含领域规则。
  3. 领域层(Domain): 核心业务逻辑、实体、值对象、领域服务、聚合根。是整个系统的核心,零依赖基础设施。
  4. 基础设施层(Infrastructure): 数据库访问、消息队列、文件系统、外部API调用。提供具体的技术实现。

依赖方向为单向:展示层 → 应用层 → 领域层 ← 基础设施层(通过接口反向依赖)。

分层架构边界与依赖方向(含依赖倒置) 展示层(Presentation) Controller / View / DTO 职责:接收输入、格式化输出、路由分发 应用层(Application) ApplicationService / UseCase / DTO Assembler 职责:用例编排、事务控制、权限验证 领域层(Domain) Entity / ValueObject / DomainService / Repository 接口 职责:核心业务规则,零基础设施依赖 基础设施层(Infrastructure) Repository实现 / DB / MQ / External API 职责:提供具体技术实现,实现领域层定义的接口 依赖 依赖 实现领域接口(依赖倒置) 依赖倒置核心 领域层定义接口(Repository) 基础设施层实现接口

4.3 数据流与控制流边界

数据流和控制流在分层边界中具有不同的穿透规则:

  • 控制流: 总是从上层发起,逐层向下传递。展示层调用应用层,应用层调用领域层。领域层的回调通过接口反向传递给基础设施层(例如依赖注入框架控制对象创建)。
  • 数据流: 跨层传递时,必须使用边界对象(DTO、VO)。禁止将领域实体直接暴露给展示层,避免遗漏聚合边界或触发懒加载异常。

高风险操作: 将领域实体(Entity)直接作为 JSON 响应序列化返回给前端。这会导致:1) 聚合边界被破坏;2) 序列化触发未加载的关联数据(N+1问题);3) 内部状态暴露给外部。必须使用 DTO 进行数据裁剪。

4.4 信任链路与安全边界

分层架构中的信任是递减的:领域层是最信任的核心,而展示层和基础设施层是“不可信”的边缘。

信任层级 安全策略 举例
展示层 输入校验(格式、长度、类型) JSR-303 Bean Validation
应用层 权限校验、会话验证、防重放 Spring Security @PreAuthorize
领域层 业务规则不变性、聚合内一致性 实体方法中校验状态约束
基础设施层 SQL注入防护、连接池管理、限流 MyBatis参数绑定、Redis连接池

4.5 选型权衡与风险点

分层架构并非银弹,在以下场景中需要权衡:

  • 过度分层导致“垃圾代码”: 如果业务逻辑非常简单(如纯 CRUD),四层架构会产生大量的样板代码和参数传递。此时应退化为“三层架构”或“扁平化”。
  • 依赖注入陷阱: 过度使用框架注入会导致对象创建和生命周期控制权完全交给容器,边界变得不可见。需要对注入点进行审计,确保只有应用层和基础设施层之间允许使用注入。
  • 性能开销: 每次跨层调用都会产生对象复制和上下文切换。在极高性能要求的场景下(如网关、实时计算),需要用 struct 或 Span 这类低开销的边界对象。

4.6 落地步骤

启用分层架构边界策略的推荐步骤:

  1. 定义模块结构: 按照层创建 Maven/Gradle 模块(presentation、application、domain、infrastructure),在 build.gradle 中严格配置编译依赖方向。
  2. 编写依赖规则脚本: 使用 ArchUnit 或自定义 Checkstyle 规则,在单元测试阶段强制校验层间依赖。
  3. 实现依赖倒置: 在领域层定义 Repository 接口,在基础设施层实现。应用层使用构造函数注入接口。
  4. 建立 DTO 映射层: 在应用层或展示层定义 xxxDTO,使用 MapStruct 或手工转换,确保领域实体不直接暴露。
// 领域层:定义边界接口(无外部依赖)
public interface AccountRepository {
    Account findById(String accountId);
    void save(Account account);
}

// 基础设施层:实现接口(依赖Spring Data JPA)
@Service
public class AccountRepositoryImpl implements AccountRepository {
    @Autowired
    private AccountJpaRepository jpaRepository;
    
    @Override
    public Account findById(String accountId) {
        AccountEntity entity = jpaRepository.findById(accountId)
            .orElseThrow(() -> new AccountNotFoundException(accountId));
        return entity.toDomain(); // 基础设施层内部转换
    }
}

// 应用层:使用接口,不关心实现
@Service
public class TransferService {
    private final AccountRepository accountRepository;
    
    public TransferService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository; // 依赖注入
    }
    
    public void transfer(String fromId, String toId, Money amount) {
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        from.withdraw(amount);
        to.deposit(amount);
        accountRepository.save(from);
        accountRepository.save(to);
    }
}

4.7 验收标准

完成本章的边界策略实施后,应通过以下验收检查:

  1. 编译期隔离: 尝试在领域层的 Java 文件中导入 javax.persistence、org.springframework.web、java.sql.Connection 等基础设施类时,编译报错。
  2. 单元测试可模拟: 领域层所有核心用例可以完全通过 Mock 基础设施接口进行单元测试,不需要启动数据库或 Web 容器。
  3. 依赖方向验证: ArchUnit 测试通过,确保不存在应用层直接调用基础设施层(除非经过接口间接调用)的情况。
  4. DTO 隔离: 在展示层和领域层之间没有直接的类型引用(如展示层不 import 领域层 Entity)。