为什么 Clean Architecture 让所有人感到困惑(以及我如何学会不再担心)
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 就能测试它们。其他的一切都只是细节。