谈整洁架构的实践

谈整洁架构的实践

《整洁架构之道》是一本有关软件架构"道"的书------它详细阐述了为什么要这么做、高层级的原则(如依赖原则、边界划分),但没有说明如何在具体的技术栈(比如 Spring Boot + React 中)落地。从"知道"到"做到",中间需要填补大量的技术细节和决策。要把整洁架构落地到实际项目,可以尝试以下几个具体的切入点和方法。

整洁架构

架构模型

理解

在软件架构设计中,遵循 SOLID 原则是构建高质量系统的基石。在代码的组织与架构上,需要把握以下两个核心维度:职责划分依赖管理

  1. 职责的单一性与边界划分
    • 依据单一职责原则对类、模块及组件进行精细化切分,确保每个单元有且仅有一个变更的理由。
    • 在此基础上,通过清晰的架构边界 来隔离不同的业务维度和变化速率。这种边界划分能够有效地将高频变化的细节 (如UI、外部集成、基础设施)与稳定的核心领域逻辑分离开来。
  2. 依赖关系的控制与稳定性保障
    • 系统被组织为内聚的层次结构:核心层(内层/高层) 封装了企业级的业务逻辑,是系统最稳定的部分,其变更极为罕见但影响深远;实现层(外层/低层) 则包含具体的实现细节(如框架、数据库、API网关),虽然变更较为频繁,但其影响被限制在边界之内。
    • 为了确保核心逻辑的绝对稳定,必须严格遵循依赖倒置原则 :即源码级别的依赖关系必须指向内层 。这意味着外层(低层/易变模块)应当依赖内层(高层/稳定模块)定义的抽象接口,而非具体实现。
    • 这种设计体现了稳定依赖原则 ------依赖的方向必须指向更稳定的方向;以及稳定抽象原则------越稳定的模块,应该越抽象。通过将易变的对象隐藏在接口之后,外层的任何变动都不会波及稳定的核心业务逻辑。
    • 最终收益:通过控制依赖流向,我们确保了核心业务逻辑的稳定性,从而极大地降低了因外部需求或技术选型变更所带来的系统改造成本与人力投入。

从包结构开始,强制实施依赖规则

整洁架构最直观的体现就是代码的包(Package)结构。这是最容易上手、也最强制的手段。

不要按"层"分包(传统的三层架构):

复制代码
com.myapp
├── controller
├── service
├── repository
└── entity

而要按"功能"和"架构边界"分包:

复制代码
com.myapp
├── 1. 实体层 (Enterprise Business Rules)
│   └── User.java (纯业务对象,可能是一个普通的POJO)
│
├── 2. 用例层 (Application Business Rules)
│   ├── ports      (接口)
│   │   ├── input  (接收来自外层的数据,如:CreateUserUseCase.java)
│   │   └── output (告诉外层需要做什么,如:UserRepositoryPort.java)
│   └── services   (实现用例,如:CreateUserService.java)
│
├── 3. 接口适配器层 (Interface Adapters)
│   ├── web
│   │   ├── controllers (实现/调用 input 接口)
│   │   └── dtos        (请求/响应模型)
│   └── persistence
│       ├── repositories (实现 output 接口)
│       └── jpa          (JPA 实体,数据库映射对象)
│
└── 4. 框架层 (Frameworks & Drivers)
    └── config (Spring 配置,数据库连接等)

关键点: 这种分层的核心是依赖方向。webpersistence 都依赖于中间的 services(通过实现/调用接口),而 services 不依赖外部。可以通过编译时检查来验证这一点。

严格定义接口(Port)

整洁架构中,内外通信全靠接口,这是最具实践性的技术点。

  • Input Port(入站接口): 定义在用例层。例如 interface CreateUserUseCase { User execute(UserDto dto); }
    • 实践: 外部的 Controller 不直接依赖 Service 实现类,而是依赖这个接口。这确保了 Controller 不知道业务逻辑是如何实现的。
  • Output Port(出站接口): 也定义在用例层。例如 interface UserRepositoryPort { User save(User user); }
    • 实践: 业务逻辑层(Service)不直接调用数据库 Repository 的具体方法,而是调用这个接口。数据库层的 Repository 实现类去实现这个接口。

关注用例(Use Case)

书中反复强调"业务逻辑"在用例层。整洁架构的实践(体现业务逻辑):

java 复制代码
// 用例层 services/CreateUserService.java
public class CreateUserService implements CreateUserUseCase {
    private final UserRepositoryPort userRepository;
    private final EmailValidationPort emailValidator; // 另一个出站接口(比如调用第三方API)

    public User execute(UserDto userDto) {
        // 1. 业务规则:不能创建同名用户
        if (userRepository.existsByName(userDto.getName())) {
            throw new BusinessException("用户名已存在");
        }
        // 2. 业务规则:邮箱必须有效(调用外部的验证服务)
        if (!emailValidator.isValid(userDto.getEmail())) {
            throw new BusinessException("邮箱无效");
        }
        // 3. 创建实体
        User user = new User(userDto.getName(), userDto.getEmail());
        // 4. 保存
        return userRepository.save(user);
    }
}

这里的核心是:用例层编排了所有的业务规则,它告诉外层(通过 Port)我需要什么,而不是被外层牵着鼻子走。

拥抱 实体 与 数据模型 的分离

这是实践中最痛苦但也最值得坚持的一点。

  • 领域实体(Entity): 在核心层,是一个纯内存对象。它包含行为(方法),不包含任何注解(如 @Entity, @Column),不继承任何框架类。
  • 数据模型(Data Model): 在持久化层,是被 ORM(如 JPA)注解的对象,它只是数据的载体。

实践技巧:

在用例层(Service)中,操作的是领域实体。

在持久化层(Repository Impl)中,需要将领域实体转换为数据模型进行保存,读取时再转换回来。

java 复制代码
// 持久化层 repositories/UserRepositoryImpl.java
public class UserRepositoryImpl implements UserRepositoryPort {
    private final JpaRepository jpa; // Spring Data JPA 接口

    @Override
    public User save(User user) { // 这里的 User 是核心层的 Entity
        // 1. 核心实体 -> JPA 实体
        UserJpaEntity jpaEntity = new UserJpaEntity(user.getName(), user.getEmail());
        // 2. 保存
        UserJpaEntity saved = jpa.save(jpaEntity);
        // 3. JPA 实体 -> 核心实体
        return new User(saved.getId(), saved.getName(), saved.getEmail());
    }
}

这样做的好处是,的核心业务逻辑完全不依赖数据库的特定实现。即使某天要把 JPA 换成 MyBatis,甚至换成文件系统,核心层可以纹丝不动。

接受 额外的工作量,从 关键核心 开始

如果尝试在整个项目中推行整洁架构,可能会遇到挫败感,因为会发现自己写了大量的接口、转换逻辑,而项目进展却变慢了。

实践建议:

不需要重构整个项目,只需要在核心业务模块(如订单处理、支付、复杂的风控逻辑)中实践。

  • 对于简单的、只做 CRUD 的管理后台(如简单的数据字典配置),直接使用传统三层架构反而更高效。
  • 对于业务逻辑复杂、未来可能变化、需要长期维护的核心模块,引入整洁架构。

具体的代码层面检查清单

在写代码时,可以随时问自己几个问题:

  1. 用例层(Service)能否独立于 Spring 运行?(除了接口定义,不应该有任何 Spring 注解,如 @Autowired 可以放到构造器里,但最好只是普通的 Java 对象)。
  2. 核心的实体(Entity)是否有 JPA 注解?(如果有,说明架构腐化了,数据层入侵了核心层)。
  3. Controller 是否直接返回了数据库实体?(不应该,应该返回 DTO,并且 Controller 应该调用的是 UseCase 接口,而不是 Service 实现类)。
  4. 业务逻辑是否散落在 Controller 或 Repository 中?(如果是,需要收拢到 UseCase 里)。

最后

《整洁架构之道》描绘的是一种理想化的、极度纯净的"道",而现实中的企业项目,则是在各种约束下寻求平衡的"术"。这两者之间存在巨大差异,是非常正常的。关于这本书的理念在企业中的采用情况,一个更贴近现实的图景是:完全照搬的极少,但核心思想被普遍借鉴和吸收。整洁架构的理念也必须与项目的具体规模、团队结构、业务压力相结合,才能落地生根。本书的价值在于给了一把尺子。当开始实践时,遇到"这个逻辑该放哪"的困惑时,再回过头翻翻这本书,对照一下依赖关系图,答案就会清晰很多。


愿我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

相关推荐
小付爱coding2 小时前
openclaw源码架构深度解析【总体概况】
python·架构·openclaw
Swift社区2 小时前
ArkUI 的状态管理,其实很多人都用错了
架构·harmonyos
隔壁小邓2 小时前
在Java中实现优雅的CQRS架构
java·开发语言·架构
IOT-Power3 小时前
QT 事件驱动架构
开发语言·qt·架构
arvin_xiaoting3 小时前
三角协作架构:从问题发现到验证完成
架构·系统架构·llm·claude·ai agent·openclaw·多代理协作
心.c4 小时前
从 ReAct 到 Plan-and-Execute:AI Agent 推理架构的理解与选择
人工智能·react.js·架构
刘晨鑫14 小时前
LNMP网站架构与部署
架构
Mintopia4 小时前
架构师的心胸:把分歧装进系统,把人放在方案前
前端·架构