洁净架构:依赖倒置与同心圆
洁净架构:依赖倒置与同心圆
洁净架构概述
洁净架构(Clean Architecture)由Robert C. Martin提出,是一种通过依赖倒置原则将业务逻辑与外部依赖解耦的架构模式。其核心思想是:业务规则应该独立于框架、UI、数据库或其他外部因素。
同心圆结构
洁净架构采用同心圆结构,从内到外依次是:
┌─────────────────────────────────────┐
│ 框架与驱动器 (Frameworks) │
│ ┌─────────────────────────────┐ │
│ │ 接口适配器 (Interfaces) │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ 应用业务规则 │ │ │
│ │ │ (Use Cases) │ │ │
│ │ │ ┌─────────────┐ │ │ │
│ │ │ │ 企业业务规则 │ │ │ │
│ │ │ │ (Entities) │ │ │ │
│ │ │ └─────────────┘ │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
各层职责
- 企业业务规则(Entities):最内层,包含核心业务实体和业务规则
- 应用业务规则(Use Cases):编排业务流程,协调实体完成特定用例
- 接口适配器(Interfaces):将数据转换为实体或用例期望的格式
- 框架与驱动器(Frameworks):最外层,包含框架、数据库、UI等外部依赖
依赖倒置原则
依赖倒置原则(DIP)是洁净架构的核心:高层模块不应该依赖低层模块,两者都应该依赖抽象。
// 企业业务规则层 (Entity)
public class Order {
private Long id;
private List<OrderItem> items;
private OrderStatus status;
private BigDecimal totalAmount;
// 业务规则
public void addItem(OrderItem item) {
this.items.add(item);
recalculateTotal();
}
public void cancel() {
if (status == OrderStatus.SHIPPED) {
throw new BusinessException("Cannot cancel shipped order");
}
this.status = OrderStatus.CANCELLED;
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
// 应用业务规则层 (Use Case)
public class CreateOrderUseCase {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
public CreateOrderUseCase(OrderRepository orderRepository, InventoryService inventoryService) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
}
public Order execute(CreateOrderCommand command) {
// 1. 验证库存
inventoryService.checkAvailability(command.getItems());
// 2. 创建订单实体
Order order = OrderFactory.create(command);
// 3. 保留库存
inventoryService.reserve(order.getItems());
// 4. 保存订单
return orderRepository.save(order);
}
}
// 接口定义(抽象)
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(Long id);
List<Order> findByCustomerId(Long customerId);
}
// 接口适配器层 (Repository实现)
@Repository
public class SqlOrderRepository implements OrderRepository {
private final OrderJpaRepository jpaRepository;
private final OrderMapper mapper;
@Override
public Order save(Order order) {
OrderJpaEntity entity = mapper.toJpaEntity(order);
OrderJpaEntity savedEntity = jpaRepository.save(entity);
return mapper.toDomainEntity(savedEntity);
}
}
// 框架与驱动器层 (Controller)
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final CreateOrderUseCase createOrderUseCase;
private final OrderPresenter presenter;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
CreateOrderCommand command = presenter.toCommand(request);
Order order = createOrderUseCase.execute(command);
OrderResponse response = presenter.toResponse(order);
return ResponseEntity.ok(response);
}
}
依赖规则
洁净架构最重要的规则是依赖规则:源代码依赖只能指向内层。
// 正确:使用依赖倒置
public class CreateOrderUseCase {
// 依赖抽象(接口),而不是具体实现
private final OrderRepository orderRepository;
public CreateOrderUseCase(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
// 错误:直接依赖具体实现
public class CreateOrderUseCase {
// 直接依赖具体实现,违反依赖规则
private final JpaOrderRepository jpaOrderRepository;
public CreateOrderUseCase(JpaOrderRepository jpaOrderRepository) {
this.jpaOrderRepository = jpaOrderRepository;
}
}
边界跨越
层与层之间的边界通过数据结构跨越,每个边界使用不同的数据结构:
// 企业层 - 领域实体
public class Order {
private OrderId id;
private List<OrderItem> items;
private Money totalAmount;
}
// 应用层 - 命令/查询对象
public class CreateOrderCommand {
private Long customerId;
private List<OrderItemCommand> items;
}
// 接口层 - DTO
public class OrderDto {
private Long id;
private Long customerId;
private List<OrderItemDto> items;
private BigDecimal totalAmount;
}
// 数据层 - JPA实体
@Entity
@Table(name = "orders")
public class OrderJpaEntity {
@Id
@GeneratedValue
private Long id;
private Long customerId;
private BigDecimal totalAmount;
}
测试策略
洁净架构使得测试变得简单,因为业务逻辑不依赖外部框架:
// 测试业务逻辑,不需要启动Spring容器
class CreateOrderUseCaseTest {
private CreateOrderUseCase useCase;
private InMemoryOrderRepository orderRepository;
private MockInventoryService inventoryService;
@BeforeEach
void setUp() {
orderRepository = new InMemoryOrderRepository();
inventoryService = new MockInventoryService();
useCase = new CreateOrderUseCase(orderRepository, inventoryService);
}
@Test
void shouldCreateOrderSuccessfully() {
// Given
CreateOrderCommand command = new CreateOrderCommand(
1L,
List.of(new OrderItemCommand(100L, 2))
);
inventoryService.mockAvailable(100L, 10);
// When
Order order = useCase.execute(command);
// Then
assertNotNull(order.getId());
assertEquals(OrderStatus.CREATED, order.getStatus());
assertEquals(1, order.getItems().size());
}
}
洁净架构的优点
- 框架独立:业务逻辑不依赖特定框架
- 可测试性:业务逻辑可以在没有UI、数据库或外部服务的情况下测试
- UI独立:可以轻松更改UI而不影响业务逻辑
- 数据库独立:可以轻松更换数据库而不影响业务逻辑
- 外部代理独立:业务规则不知道外部世界的细节
实施建议
- 从内向外构建:先定义实体和用例,再实现接口和框架
- 使用依赖注入:通过构造函数注入依赖,便于测试和替换
- 定义清晰的边界:每个层使用独立的数据结构
- 保持实体纯净:实体不应包含框架特定的注解或代码
- 使用接口定义契约:所有跨边界交互都通过接口进行