整洁架构与洋葱架构

Uncle Bob 的四层圆圈

2012年,Robert Martin(Uncle Bob)在他的博客上画了一个著名的四层圆圈图:

     ┌─────────────────────────────────────────┐
     │   Frameworks & Drivers                   │
     │  ┌───────────────────────────────────┐  │
     │  │   Interface Adapters               │  │
     │  │  ┌─────────────────────────────┐  │  │
     │  │  │   Use Cases (Application)   │  │  │
     │  │  │  ┌─────────────────────┐  │  │  │
     │  │  │  │      Entities        │  │  │  │
     │  │  │  └─────────────────────┘  │  │  │
     │  │  └─────────────────────────────┘  │  │
     │  └───────────────────────────────────┘  │
     └─────────────────────────────────────────┘

依赖方向:外向内(箭头指向内层)

这张图看起来简单,但能真正理解并落地的人不多。


一、整洁架构四层详解🔴

1.1 第4层:实体(Entities)

核心中的核心:包含企业级业务规则,是系统中最稳定、最不易变化的部分。

// 实体:订单聚合根
class Order {
    private OrderId id;
    private UserId userId;
    private List<OrderItem> items;
    private OrderStatus status;

    // 核心业务规则
    public void pay(Money amount) {
        // 前置条件
        if (this.status != OrderStatus.CREATED) {
            throw new OrderCannotPayException();
        }

        // 业务规则
        if (!amount.equals(calculateTotal())) {
            throw new AmountMismatchException();
        }

        this.status = OrderStatus.PAID;
    }

    // 业务规则:计算总价
    private Money calculateTotal() {
        return items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }
}

1.2 第3层:用例(Use Cases)

应用业务规则:编排实体的操作,定义系统的功能点。

// 用例:创建订单
class CreateOrderUseCase {
    private final OrderRepository orderRepository;
    private final UserFacade userFacade;
    private final EventPublisher eventPublisher;

    public Order execute(CreateOrderCommand command) {
        // 1. 获取用户信息
        User user = userFacade.findById(command.getUserId())
            .orElseThrow(() -> new UserNotFoundException());

        // 2. 校验用户状态
        if (!user.isActive()) {
            throw new UserInactiveException();
        }

        // 3. 创建订单(使用实体)
        Order order = new Order();
        order.setUserId(user.getId());
        order.addItems(command.getItems());

        // 4. 保存
        orderRepository.save(order);

        // 5. 发布事件
        eventPublisher.publish(new OrderCreatedEvent(order));

        return order;
    }
}

1.3 第2层:接口适配器(Interface Adapters)

转换器:把外部数据(HTTP、DB)转换为用例可用的格式。

// Controller:HTTP → Command
@RestController
class OrderController {
    @Autowired
    private CreateOrderUseCase createOrderUseCase;

    @PostMapping("/orders")
    public ResponseEntity<OrderResponse> create(@RequestBody CreateOrderRequest request) {
        // HTTP Request → Command
        CreateOrderCommand command = new CreateOrderCommand();
        command.setUserId(request.getUserId());
        command.setItems(request.getItems());

        // 执行用例
        Order order = createOrderUseCase.execute(command);

        // Order → Response
        return ResponseEntity.ok(toResponse(order));
    }
}

// Repository 实现:接口 → MySQL
class MySqlOrderRepository implements OrderRepository {
    private final JdbcTemplate jdbcTemplate;

    @Override
    public void save(Order order) {
        jdbcTemplate.update(
            "INSERT INTO orders (id, user_id, status) VALUES (?, ?, ?)",
            order.getId(), order.getUserId(), order.getStatus()
        );
    }
}

1.4 第1层:框架和驱动

基础设施:数据库、Web 框架、消息队列、缓存等。

@Configuration
class AppConfig {
    // Spring Boot 自动配置
    // - DataSource
    // - RedisTemplate
    // - KafkaTemplate
    // - Web Server
}

// 这些都是外部依赖,最外层

二、依赖方向原则🔴

2.1 核心原则

整洁架构的核心规则:依赖只能指向内层

第4层(Entities)    ← 不依赖任何层
第3层(Use Cases)   ← 只依赖 Entities
第2层(Adapters)    ← 只依赖 Use Cases
第1层(Frameworks)  ← 只依赖 Adapters

不能反过来:
❌ Use Cases 依赖 Controllers
❌ Entities 依赖 Repositories

2.2 依赖倒置实现

// ❌ 错误:Use Case 直接依赖数据库接口
class CreateOrderUseCase {
    private final OrderDao orderDao; // 数据库接口

    public void execute(Order order) {
        orderDao.insert(order); // 直接依赖底层
    }
}

// ✅ 正确:通过端口(接口)间接依赖
// 端口接口(在 Use Case 层定义)
interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(OrderId id);
}

// 适配器实现(在 Infrastructure 层)
class JpaOrderRepository implements OrderRepository {
    private final JpaRepository repo;

    @Override
    public void save(Order order) {
        repo.save(order);
    }
}

// Use Case 只依赖端口
class CreateOrderUseCase {
    private final OrderRepository repository; // 依赖抽象,不依赖具体

    public void execute(Order order) {
        repository.save(order);
    }
}

三、洋葱架构对比🟡

3.1 洋葱架构的结构

洋葱架构(Onion Architecture):

     ┌─────────────────────────────────┐
     │        UI, Web, Infrastructure   │
     │    ┌─────────────────────────┐  │
     │    │    Application Services  │  │
     │    │  ┌─────────────────────┐ │  │
     │    │  │   Domain Services   │ │  │
     │    │  │  ┌───────────────┐  │ │  │
     │    │  │  │    Domain     │  │ │  │
     │    │  │  │   Entities    │  │ │  │
     │    │  │  │  Repositories │  │ │  │
     │    │  │  │   (Ports)    │  │ │  │
     │    │  │  └───────────────┘  │ │  │
     │    │  └─────────────────────┘ │  │
     │    └─────────────────────────┘  │
     └─────────────────────────────────┘

核心特点:
- Repository 是端口(Port),不是实现
- Domain 层在最中心
- 所有依赖指向中心

3.2 整洁 vs 洋葱:关键区别

维度整洁架构洋葱架构
层数4 层多层(可自定义)
Repository在最内层(实体旁)在最内层(称为 Port)
领域服务单独一层可选
依赖注入DI 容器构造函数注入
适用场景通用复杂业务

3.3 统一理解

两种架构本质上是一样的:

整洁架构          洋葱架构
─────────────────────────────────
Frameworks    =  Infrastructure(最外层)
Adapters      =  UI, Web(应用服务层)
Use Cases     =  Application Services
Entities      =  Domain Entities + Repositories(最内层)

四、实战落地🟡

4.1 目录结构

com.example.order
├── domain                    # 第4层:Entities
│   ├── model
│   │   ├── Order.java        # 聚合根
│   │   ├── OrderItem.java    # 值对象
│   │   └── Money.java        # 值对象
│   ├── repository
│   │   └── OrderRepository.java  # 端口接口
│   └── event
│       └── DomainEvent.java

├── application               # 第3层:Use Cases
│   ├── usecase
│   │   ├── CreateOrderUseCase.java
│   │   └── PayOrderUseCase.java
│   └── dto
│       ├── CreateOrderCommand.java
│       └── OrderResponse.java

├── infrastructure            # 第1层:Frameworks
│   ├── persistence
│   │   ├── JpaOrderRepository.java
│   │   └── jpa
│   │       └── OrderJpaEntity.java
│   ├── messaging
│   │   └── KafkaEventPublisher.java
│   └── cache
│       └── RedisCacheAdapter.java

└── interfaces                # 第2层:Adapters
    ├── controller
    │   └── OrderController.java
    └── mapper
        └── OrderDtoMapper.java

4.2 Maven/Gradle 模块划分

<!-- 方式一:按层划分模块 -->
<modules>
    <module>domain</module>          <!-- 第4层:只有实体和接口 -->
    <module>application</module>     <!-- 第3层:Use Cases -->
    <module>infrastructure</module>   <!-- 第1层:MySQL、Redis -->
    <module>interfaces</module>      <!-- 第2层:HTTP、Controller -->
</modules>

<!-- domain 模块不依赖任何其他模块 -->
<!-- application 依赖 domain -->
<!-- infrastructure 依赖 domain -->
<!-- interfaces 依赖 application 和 domain -->

4.3 Spring Boot 集成

// Application 层
@Configuration
class ApplicationConfig {
    @Bean
    public CreateOrderUseCase createOrderUseCase(
            OrderRepository orderRepository,
            UserFacade userFacade,
            EventPublisher eventPublisher) {
        return new CreateOrderUseCase(orderRepository, userFacade, eventPublisher);
    }
}

// Infrastructure 层
@Repository
class JpaOrderRepository implements OrderRepository {
    @Autowired
    private JpaRepository repo;

    @Override
    public void save(Order order) {
        repo.save(toEntity(order));
    }
}

// Interfaces 层
@RestController
class OrderController {
    @Autowired
    private CreateOrderUseCase createOrderUseCase;

    @PostMapping("/orders")
    public OrderResponse create(@RequestBody CreateOrderRequest request) {
        return createOrderUseCase.execute(toCommand(request));
    }
}

五、常见误区🟡

5.1 误区一:实体里放太多东西

// ❌ 错误:实体里加了框架注解和持久化逻辑
@Entity
@Table(name = "orders")
class Order {
    @Id
    @GeneratedValue
    private Long id;

    @Autowired  // 错误:Entity 不应该有 Spring 注解
    private OrderRepository repository;

    public void save() {
        repository.save(this);  // 错误:Entity 不应该依赖 Repository
    }
}

// ✅ 正确:实体是纯净的 POJO
class Order {
    private OrderId id;
    private OrderStatus status;

    public void pay(Money amount) {
        // 业务逻辑
    }
}

5.2 误区二:跳过 Use Case 直接在 Controller 调用 Repository

// ❌ 错误:Controller 直接调用 Repository
@RestController
class OrderController {
    @Autowired
    private OrderRepository repository;

    @PostMapping("/orders")
    public void create(@RequestBody Order order) {
        repository.save(order);  // 绕过了业务逻辑
    }
}

// ✅ 正确:通过 Use Case
@RestController
class OrderController {
    @Autowired
    private CreateOrderUseCase useCase;

    @PostMapping("/orders")
    public void create(@RequestBody CreateOrderRequest request) {
        useCase.execute(toCommand(request));  // 经过业务逻辑
    }
}

5.3 误区三:把基础设施代码放到 Domain 层

// ❌ 错误:Domain 层引用了 Spring Data JPA
package com.example.domain;

import org.springframework.data.jpa.repository.JpaRepository;  // 错误!

interface OrderRepository extends JpaRepository<Order, Long> {
    // Domain 层不应该依赖 Spring Data
}

六、面试总结

6.1 核心追问

  1. "整洁架构的四层分别是什么?" —— 实体、用例、适配器、框架
  2. "整洁架构的核心原则是什么?" —— 依赖只能指向内层
  3. "整洁架构和六边形架构的区别?" —— 本质相同,命名和划分略有不同
  4. "如何落地整洁架构?" —— 按层划分模块、依赖倒置

6.2 级别差异

级别期望回答
P5能说出整洁架构的四层结构
P6能解释依赖方向原则,知道如何实现依赖倒置
P7有实际落地经验,能指导团队实施整洁架构