整洁架构
Bob 大叔(Robert C. Martin)在 2012 年提出了「整洁架构」(Clean Architecture)的概念。但如果你仔细看他画的同心圆,你会发现:这不是一个新发明,而是一种系统化的总结。六边形架构、洋葱架构、DCI 架构、RBAC 架构——这些模式的核心思想都被他提炼到了一个同心圆里。
整洁架构的最大贡献不是「圆形画法」,而是明确了一条铁律:源代码依赖只能指向内层。
整洁架构的核心原则
Robert C. Martin 在他的博文《整洁架构》中给出了这张经典的同心圆图:
flowchart TD
subgraph Clean["整洁架构"]
direction BT
Outer1["框架和驱动层\nFrameworks & Drivers"]
Outer2["接口适配器层\nInterface Adapters"]
Middle["应用层\nApplication Business Rules"]
Inner["实体层\nEnterprise Business Rules"]
Core["最内层无依赖"]
Outer1 -->|依赖| Outer2
Outer2 -->|依赖| Middle
Middle -->|依赖| Inner
end
核心原则:源代码依赖只能指向内层。外层机制可以影响内层机制,但内层机制绝不能影响外层机制。
这意味着:
- 实体层是最稳定的层,包含企业级业务规则
- 应用层包含应用级业务规则,依赖实体层
- 接口适配器层处理内外转换,依赖应用层
- 框架层包含具体的工具和框架,依赖接口适配器层
整洁架构的四层
第一层:实体(Entities)
实体层包含企业级业务规则,是整个架构中最稳定、最不应该变化的部分。这些规则在多个应用程序中都是通用的,不依赖于业务操作的成功或失败。
// 实体 - 企业级业务规则
// 这段代码不依赖于任何框架、数据库或 UI
public class Order {
private final OrderId id;
private final Customer customer;
private final List<OrderLine> lines;
private Money totalAmount;
private OrderStatus status;
// 构造函数是公开的,允许自由构造
public Order(OrderId id, Customer customer, List<OrderLine> lines) {
this.id = id;
this.customer = customer;
this.lines = new ArrayList<>(lines);
this.totalAmount = calculateTotal();
this.status = OrderStatus.PENDING;
}
// 核心业务规则:订单确认
public void confirm() {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException(
"只有待确认状态的订单可以确认,当前状态:" + this.status);
}
if (this.lines.isEmpty()) {
throw new IllegalStateException("订单不能为空");
}
this.status = OrderStatus.CONFIRMED;
}
// 核心业务规则:订单取消
public void cancel(String reason) {
if (this.status == OrderStatus.SHIPPED || this.status == OrderStatus.DELIVERED) {
throw new IllegalStateException("已发货或已送达的订单不能取消");
}
this.status = OrderStatus.CANCELLED;
this.cancelReason = reason;
}
private Money calculateTotal() {
return lines.stream()
.map(OrderLine::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
// Getter...
}
第二层:用例(Use Cases)
用例层包含应用级业务规则,它们依赖于实体层,但不包含业务规则本身——它们编排实体来达到业务目的。
// 用例 - 应用级业务规则
public interface CreateOrderUseCase {
/**
* 创建订单
* @param command 包含客户ID和订单明细
* @return 创建的订单ID
*/
OrderId execute(CreateOrderCommand command);
}
public interface CancelOrderUseCase {
void execute(CancelOrderCommand command);
}
// 用例实现
@Service
public class CreateOrderUseCaseImpl implements CreateOrderUseCase {
private final OrderRepository orderRepository;
private final CustomerRepository customerRepository;
private final EventPublisher eventPublisher;
public CreateOrderUseCaseImpl(
OrderRepository orderRepository,
CustomerRepository customerRepository,
EventPublisher eventPublisher) {
this.orderRepository = orderRepository;
this.customerRepository = customerRepository;
this.eventPublisher = eventPublisher;
}
@Override
@Transactional
public OrderId execute(CreateOrderCommand command) {
// 1. 获取客户
Customer customer = customerRepository.findById(command.getCustomerId())
.orElseThrow(() -> new CustomerNotFoundException(command.getCustomerId()));
// 2. 创建订单领域对象(业务规则在实体中)
Order order = new Order(
OrderId.generate(),
customer,
toOrderLines(command.getLines())
);
// 3. 持久化
orderRepository.save(order);
// 4. 发布领域事件(不关心谁会处理)
eventPublisher.publish(new OrderCreatedEvent(order.getId(), order.getCustomer().getId()));
return order.getId();
}
private List<OrderLine> toOrderLines(List<OrderLineCommand> lineCommands) {
return lineCommands.stream()
.map(cmd -> new OrderLine(cmd.getProductId(), cmd.getQuantity(), cmd.getUnitPrice()))
.collect(Collectors.toList());
}
}
第三层:接口适配器(Interface Adapters)
这一层负责把内外世界的格式转换成用例层和实体层能理解的格式。控制器(Controller)、网关(Gateway)、 presenters 都属于这一层。
// 控制器 - 接口适配器
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final CreateOrderUseCase createOrderUseCase;
private final CancelOrderUseCase cancelOrderUseCase;
private final GetOrderUseCase getOrderUseCase;
public OrderController(
CreateOrderUseCase createOrderUseCase,
CancelOrderUseCase cancelOrderUseCase,
GetOrderUseCase getOrderUseCase) {
this.createOrderUseCase = createOrderUseCase;
this.cancelOrderUseCase = cancelOrderUseCase;
this.getOrderUseCase = getOrderUseCase;
}
@PostMapping
public ResponseEntity<CreateOrderResponse> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
CreateOrderCommand command = toCommand(request);
OrderId orderId = createOrderUseCase.execute(command);
return ResponseEntity
.created(URI.create("/api/v1/orders/" + orderId.getValue()))
.body(new CreateOrderResponse(orderId.getValue()));
}
@PostMapping("/{id}/cancel")
public ResponseEntity<Void> cancelOrder(
@PathVariable String id,
@Valid @RequestBody CancelOrderRequest request) {
CancelOrderCommand command = new CancelOrderCommand(
OrderId.of(id),
request.getReason()
);
cancelOrderUseCase.execute(command);
return ResponseEntity.noContent().build();
}
@GetMapping("/{id}")
public ResponseEntity<OrderDetailResponse> getOrder(@PathVariable String id) {
OrderDetail detail = getOrderUseCase.execute(OrderId.of(id));
return ResponseEntity.ok(toResponse(detail));
}
private CreateOrderCommand toCommand(CreateOrderRequest request) {
List<OrderLineDTO> lines = request.getLines().stream()
.map(l -> new OrderLineDTO(l.getProductId(), l.getQuantity(), l.getUnitPrice()))
.collect(Collectors.toList());
return new CreateOrderCommand(request.getCustomerId(), lines);
}
}
第四层:框架与驱动(Frameworks & Drivers)
这是最外层,包含具体的工具和框架:数据库、Web 框架、消息队列、缓存、第三方 API 客户端等。
// 数据库实现 - 框架层
@Repository
public class JpaOrderRepository implements OrderRepository {
@Autowired
private OrderJpaEntityRepository jpaRepository;
@Override
public void save(Order order) {
OrderJpaEntity entity = toEntity(order);
jpaRepository.save(entity);
}
@Override
public Optional<Order> findById(OrderId id) {
return jpaRepository.findById(id.getValue())
.map(this::toDomain);
}
@Override
public List<Order> findByCustomerId(CustomerId customerId) {
return jpaRepository.findByCustomerId(customerId.getValue())
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
private OrderJpaEntity toEntity(Order order) {
// 转换逻辑...
}
private Order toDomain(OrderJpaEntity entity) {
// 转换逻辑...
}
}
依赖倒置在整洁架构中的应用
整洁架构通过依赖倒置(Dependency Inversion)来实现源代码依赖指向内层:
flowchart TD
subgraph Entities["实体层"]
Order["Order"]
end
subgraph UseCases["用例层"]
CreateOrder["CreateOrderUseCase"]
CreateOrderImpl["CreateOrderUseCaseImpl"]
end
subgraph Adapters["接口适配器层"]
Controller["OrderController"]
Repository["OrderRepository"]
end
subgraph Frameworks["框架层"]
JpaRepo["JpaOrderRepository"]
end
Controller --> CreateOrder
Controller --> CreateOrderImpl
CreateOrderImpl --> Repository
JpaRepo -.->|实现| Repository
实线箭头表示编译时依赖(源代码依赖),虚线表示运行时依赖(实例注入)。
关键是:
- 用例实现依赖仓储接口(编译时)
- JPA 仓储实现实现仓储接口(运行时通过 DI 注入)
这样,用例实现不需要知道它用的是 MySQL 还是 PostgreSQL。
// 用例实现 - 只依赖接口
@Service
public class CreateOrderUseCaseImpl implements CreateOrderUseCase {
private final OrderRepository orderRepository; // 接口
private final CustomerRepository customerRepository; // 接口
public CreateOrderUseCaseImpl(
OrderRepository orderRepository,
CustomerRepository customerRepository) {
this.orderRepository = orderRepository;
this.customerRepository = customerRepository;
}
}
// Spring 配置 - 绑定运行时实现
@Configuration
public class DependencyConfig {
@Bean
public OrderRepository orderRepository(JpaOrderRepository jpaImpl) {
return jpaImpl; // 运行时注入 JPA 实现
}
@Bean
public CreateOrderUseCase createOrderUseCase(OrderRepository orderRepository, ...) {
return new CreateOrderUseCaseImpl(orderRepository, ...);
}
}
整洁架构 vs 六边形 vs 洋葱
这三种架构经常被放在一起比较,因为它们确实非常相似。核心思想都是依赖指向内部。区别在于命名和层次划分:
本质是相同的:都是通过依赖倒置,让核心业务逻辑不依赖外部世界。
整洁架构 vs 分层架构
最常见的对比是整洁架构与三层架构:
三层架构的问题在于:业务逻辑层依赖数据访问层。当数据库变了,可能影响业务逻辑。整洁架构通过依赖倒置解决了这个问题。
适用场景与不适用场景
经验之谈
整洁架构、六边形、洋葱——很多人纠结用哪个。实际上,它们的核心是一样的:依赖指向内层。选择哪个主要看:
- 团队熟悉度:用团队最熟悉的那个
- 项目需要:如果需要和 DDD 结合,洋葱更自然
- 文档丰富度:整洁架构因为 Bob 大叔的背书,资料更多
不要为了「用整洁架构」而用它。如果你的项目不需要切换数据库、不需要独立测试核心逻辑,那么普通分层就够了。
总结
整洁架构的核心贡献是明确了一条依赖规则:源代码依赖只能指向内层。这条规则保证了:
- 核心业务逻辑稳定——不随框架、数据库、UI 的变化而变化
- 测试不依赖外部——可以在不启动数据库的情况下测试核心逻辑
- 可以独立开发——外层可以并行开发,最后集成
整洁架构、六边形架构、洋葱架构都是同一个思想的不同表述。理解了「依赖倒置」这个核心,就理解了所有这些架构模式。
接下来让我们看看 CQRS,它是一种完全不同的架构思路:分离读写操作。