整洁架构模式:Bob大叔的依赖法则与分层哲学
整洁架构(Clean Architecture)是 Robert C. Martin(我们圈内都叫他 Bob大叔)在 2012 年左右系统提出的一套架构思想。说实话,我第一次看到那篇博客时,第一反应是——这不就是分层架构的加强版吗?后来在几个大型项目中反复实践,才慢慢品出其中的味道。
整洁架构的核心,说白了就一句话:让业务逻辑独立于框架、UI、数据库和任何外部工具。你想想看,我们多少项目被 Spring 绑架、被 Hibernate 拖累、被前端框架牵着鼻子走?整洁架构就是要打破这种局面。
依赖规则:一切依赖向内
整洁架构最核心的规则,我称之为「洋葱依赖法则」。它规定:源代码的依赖关系只能从外层指向内层,内层对外层一无所知。
举个例子,我在一个电商项目中遇到过这样的场景:业务层直接调用了 MyBatis 的 SqlSession。后来要换数据库,结果业务代码改得面目全非。这就是典型的依赖方向搞反了。
- 内层定义接口,外层实现接口
- 内层不引用外层的任何类、方法或变量
- 外层可以依赖内层,但内层绝不能依赖外层
- 跨层传递数据使用简单的数据结构(如 DTO 或基本类型)
这里有个容易踩的坑——依赖倒置不是依赖注入。我曾经见过一个团队,他们用 Spring 的 @Autowired 把 DAO 注入到领域服务里,就宣称自己实现了整洁架构。嗯,这其实是两码事。依赖注入只是手段,依赖倒置才是目的。
四层架构:从外到内的职责划分
Bob 大叔把系统分成四个圈层,从外到内依次是:
| 层级 | 名称 | 职责 | 典型内容 |
|---|---|---|---|
| 最外层 | 框架与驱动层 | 与外部世界的交互 | Web 框架、数据库驱动、UI 框架 |
| 第二层 | 接口适配层 | 数据格式转换、接口适配 | Controller、Presenter、Gateway 实现 |
| 第三层 | 用例层 | 编排业务逻辑、处理输入输出 | UseCase、Interactor |
| 最内层 | 实体层 | 核心业务规则 | Entity、ValueObject、DomainService |
我个人习惯把实体层和用例层合称为「业务核心」,因为它们才是系统真正值钱的部分。接口适配层和框架层说白了都是「可替换的零件」。
用例层:业务编排的核心
用例层是整洁架构中最容易被误解的一层。很多人觉得它就是个 Service 层,其实不然。用例层负责的是特定业务场景的完整流程编排,它不关心数据怎么存、页面怎么展示,只关心「这件事该怎么做」。
我举个例子。假设我们要实现「用户下单」这个功能:
// Java 示例:用例层代码
public class CreateOrderUseCase {
private final OrderRepository orderRepo;
private final PaymentGateway paymentGateway;
private final NotificationService notificationService;
// 注意:这里依赖的都是接口,不是具体实现
public CreateOrderUseCase(OrderRepository orderRepo,
PaymentGateway paymentGateway,
NotificationService notificationService) {
this.orderRepo = orderRepo;
this.paymentGateway = paymentGateway;
this.notificationService = notificationService;
}
public OrderResponse execute(CreateOrderRequest request) {
// 1. 验证请求
Order order = Order.create(request.getUserId(), request.getItems());
// 2. 处理支付
PaymentResult payment = paymentGateway.charge(order.getTotal());
if (!payment.isSuccess()) {
throw new PaymentFailedException(payment.getError());
}
// 3. 保存订单
orderRepo.save(order);
// 4. 发送通知
notificationService.sendOrderConfirmation(order);
return new OrderResponse(order.getId(), order.getStatus());
}
}
你看,这个用例类里没有任何框架注解,没有数据库操作,没有 HTTP 请求。它只做一件事:编排业务逻辑。这就是整洁架构的精髓——业务代码干干净净,不掺杂任何技术细节。
接口适配层:数据转换的守门员
接口适配层的作用,说白了就是「翻译」。它把外层的数据格式转换成内层能理解的格式,反过来也一样。我见过太多项目把 JSON 解析、DTO 转换、异常处理全部混在 Controller 里,结果 Controller 比业务代码还复杂。
整洁架构的做法是:
- Controller:接收 HTTP 请求,解析参数,调用用例层
- Presenter:把用例层的返回结果转换成视图模型
- Gateway 实现:把用例层的接口调用转换成具体的数据库操作或外部 API 调用
// C++ 示例:接口适配层的 Presenter
class OrderPresenter {
public:
// 把用例层的输出转换成 JSON 响应
JsonResponse present(const OrderResponse& response) {
JsonResponse json;
json["order_id"] = response.orderId;
json["status"] = response.status;
json["created_at"] = response.createdAt;
if (response.status == "FAILED") {
json["error"] = response.errorMessage;
return JsonResponse(400, json);
}
return JsonResponse(200, json);
}
};
这里有个关键点:Presenter 只做数据转换,不做业务判断。我曾经在一个项目中看到 Presenter 里写了 if-else 判断订单状态,这其实是业务逻辑,应该放在用例层或实体层。
实体层:最稳定的业务核心
实体层是系统的「压舱石」。它包含的是最通用、最稳定的业务规则,不依赖任何框架、数据库或外部服务。一个设计良好的实体,即使把整个系统换掉,它也能原封不动地搬过去。
举个例子,订单实体的核心规则:
// Java 示例:实体层
public class Order {
private final String orderId;
private final String userId;
private final List<OrderItem> items;
private OrderStatus status;
private final LocalDateTime createdAt;
// 工厂方法:确保订单创建时状态正确
public static Order create(String userId, List<OrderItem> items) {
if (userId == null || userId.isEmpty()) {
throw new IllegalArgumentException("userId must not be empty");
}
if (items == null || items.isEmpty()) {
throw new IllegalArgumentException("order must have at least one item");
}
return new Order(
UUID.randomUUID().toString(),
userId,
items,
OrderStatus.PENDING,
LocalDateTime.now()
);
}
// 业务方法:取消订单
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException("cannot cancel shipped order");
}
this.status = OrderStatus.CANCELLED;
}
// 计算总价
public Money getTotal() {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
}
Bob 大叔的架构思想精髓
总结一下整洁架构的核心思想,我提炼成四个原则:
- 独立于框架:架构不应该依赖某个框架的存在。框架是工具,不是约束。
- 可测试性:业务逻辑可以在没有 UI、数据库、Web 服务器的情况下单独测试。
- 独立于 UI:UI 可以轻松改变,而不需要改变业务逻辑。Web 可以换成桌面,桌面可以换成移动端。
- 独立于数据库:你可以把 Oracle 换成 MySQL,或者把 MySQL 换成 MongoDB,业务逻辑纹丝不动。
你可能会问:这样做会不会太复杂?说实话,对于简单的 CRUD 项目,整洁架构确实有点「杀鸡用牛刀」。但一旦系统复杂度上来,或者业务逻辑频繁变化,整洁架构的价值就体现出来了。我参与过一个金融项目,业务规则几乎每个月都在变,但因为采用了整洁架构,每次改动都只影响用例层,数据库和 UI 基本不用动——这种体验,真的很爽。
公众号:蓝海资料掘金营,微信deep3321