为什么 Clean Architecture 让所有人感到困惑(以及我如何学会不再担心)

发布: (2026年2月4日 GMT+8 22:03)
7 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的完整文本内容,我将为您把它翻译成简体中文,并保持原有的格式、Markdown 语法以及技术术语不变。谢谢!

框架问题

当你启动一个 Spring Boot 项目时,框架几乎在乞求你这么做:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private String customerEmail;

    @OneToMany(mappedBy = "order")
    private List items;
}

@Service
public class OrderService {
    @Autowired
    private OrderRepository repository;

    public Order save(Order order) {
        return repository.save(order);
    }
}

它能工作。编写速度快。你的 DevOps 团队很高兴,因为你交付得很快。但问题在于:你的业务逻辑现在已经了解 JPA、Jackson,以及可能还有十几种其他框架关注点

清晰的架构原则指出,领域层不应了解这些东西。然而 Spring Boot 正是如此,轻而易举地让你再添加一个注解。

按层组织的陷阱

大多数教程都会教你这种结构:

src/main/java/
├── controller/
├── service/
├── repository/
└── model/

这看起来很自然,也是一致的做法。但它完全错过了清洁架构的要点。

问题不在于文件夹本身——而是这种结构会让你倾向于从技术层面思考,而不是从业务能力出发

如果改为按功能组织的话:

src/main/java/
├── order/
│   ├── domain/
│   ├── application/
│   └── infrastructure/
├── product/
└── customer/

会出现有趣的变化:你会开始思考系统做了什么,而不是它用了哪些技术。

我在构建真实系统时学到的东西

在之前的岗位上,我曾参与高吞吐量计费系统、实时数据平台以及多租户公共行政系统的开发。以下是实际重要的点:

1. 业务逻辑应当乏味

你的核心业务规则不应关心:

  • 数据如何存储(PostgreSQL?MongoDB?Firestore?)
  • 请求如何到达(REST?GraphQL?消息队列?)
  • 使用的是哪个框架

当我需要把系统从 MongoDB 迁移到 PostgreSQL 时,痛点的程度直接反映了架构质量。如果业务逻辑与数据库注解纠缠在一起,这种迁移就会变成噩梦。

2. 没有意义的接口是噪音

不要仅仅因为有人说“清洁架构需要端口和适配器”就创建接口。只有在真正需要切换实现或隔离依赖时才创建它们。

我见过的代码库里,仓库接口只有唯一的实现,而且永远如此。这不是架构,而是形式主义。

3. 真正的考验是变更

你能否:

  • 在不触及业务逻辑的前提下,将 PostgreSQL 换成其他数据库?
  • 将 REST API 改为使用 GraphQL 而无需重写服务?
  • 将 Spring Boot 替换为 Micronaut(假设情况)?

如果答案是“可以”,说明你的架构还算不错。如果答案是“你疯了吗?”,那么文件夹结构根本无关紧要——你已经耦合在一起了。

实用的折中方案

保持业务规则清晰

public class CancelOrderUseCase {
    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;

    public void execute(Long orderId, String reason) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFound(orderId));

        if (order.isAlreadyShipped()) {
            throw new CannotCancelShippedOrder(orderId);
        }

        if (order.isPaid()) {
            paymentGateway.refund(order.getPaymentId());
        }

        order.cancel(reason);
        orderRepository.save(order);
    }
}

注意:没有 @Service,没有 @Transactional,没有 JPA 注解。只有业务逻辑。

在边界处适配

@Service
@Transactional
public class OrderService {
    private final CancelOrderUseCase useCase;

    public void cancelOrder(Long orderId, String reason) {
        useCase.execute(orderId, reason);
    }
}

现在 Spring 的东西位于边缘。业务逻辑并不知道它们。

停止过度思考,开始衡量

Instead of debating folder structures, ask yourself:

  • 测试我的业务逻辑有多难?
    如果你需要启动数据库或模拟 15 个依赖,说明有问题。

  • 框架更新多久会破坏我的代码?
    当 Spring Boot 4.0 发布时,你需要修改多少文件?

  • 新开发者能否理解系统的功能?
    如果他们必须阅读 controller → service → repository → entity 才能了解一个特性,那么你的架构就在隐藏业务逻辑。

真正重要的事

在不同行业(从金融服务到公共行政等)构建系统之后,这里是我的真实看法:

Clean architecture 并不是关于文件夹的。 它在于让业务逻辑独立于提供它的框架和工具。

你不需要在第一天就拥有完美的六边形架构。你需要确保当需求变化(而且肯定会变化)时,能够在不重写全部代码的情况下进行适配。

混乱的根源在于我们关注了错误的点。我们争论包名,而我们的领域模型却被 @Entity 注解覆盖。我们创建繁复的文件夹结构,却把业务逻辑写在 Spring 控制器里。

保持简单。让业务规则保持纯净。无需对整个世界进行 mock 就能测试它们。其他的一切都只是细节。

Back to Blog

相关文章

阅读更多 »

Java 虚拟线程 — 快速指南

Java 虚拟线程 — 快速指南 Java 21+ · Spring Boot 3.2+ · Project Loom 一个简明、面向生产的 Java 虚拟线程指南 — 它们是什么,如何…

Spring Boot 异常处理

Java 与 Spring Boot 异常处理笔记 1. 什么是 Exception? Exception = 打破程序正常流程的不期望情况。 异常处理的目标……