← 返回首页
🏗️

洁净架构:依赖倒置与同心圆

📂 architecture ⏱ 3 min 517 words

洁净架构:依赖倒置与同心圆

洁净架构概述

洁净架构(Clean Architecture)由Robert C. Martin提出,是一种通过依赖倒置原则将业务逻辑与外部依赖解耦的架构模式。其核心思想是:业务规则应该独立于框架、UI、数据库或其他外部因素

同心圆结构

洁净架构采用同心圆结构,从内到外依次是:

┌─────────────────────────────────────┐
│         框架与驱动器 (Frameworks)      │
│  ┌─────────────────────────────┐    │
│  │      接口适配器 (Interfaces)  │    │
│  │  ┌─────────────────────┐    │    │
│  │  │   应用业务规则       │    │    │
│  │  │  (Use Cases)        │    │    │
│  │  │  ┌─────────────┐    │    │    │
│  │  │  │  企业业务规则 │    │    │    │
│  │  │  │  (Entities)  │    │    │    │
│  │  │  └─────────────┘    │    │    │
│  │  └─────────────────────┘    │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

各层职责

  1. 企业业务规则(Entities):最内层,包含核心业务实体和业务规则
  2. 应用业务规则(Use Cases):编排业务流程,协调实体完成特定用例
  3. 接口适配器(Interfaces):将数据转换为实体或用例期望的格式
  4. 框架与驱动器(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());
    }
}

洁净架构的优点

  1. 框架独立:业务逻辑不依赖特定框架
  2. 可测试性:业务逻辑可以在没有UI、数据库或外部服务的情况下测试
  3. UI独立:可以轻松更改UI而不影响业务逻辑
  4. 数据库独立:可以轻松更换数据库而不影响业务逻辑
  5. 外部代理独立:业务规则不知道外部世界的细节

实施建议

  1. 从内向外构建:先定义实体和用例,再实现接口和框架
  2. 使用依赖注入:通过构造函数注入依赖,便于测试和替换
  3. 定义清晰的边界:每个层使用独立的数据结构
  4. 保持实体纯净:实体不应包含框架特定的注解或代码
  5. 使用接口定义契约:所有跨边界交互都通过接口进行