#整洁架构与洋葱架构
#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 核心追问
- "整洁架构的四层分别是什么?" —— 实体、用例、适配器、框架
- "整洁架构的核心原则是什么?" —— 依赖只能指向内层
- "整洁架构和六边形架构的区别?" —— 本质相同,命名和划分略有不同
- "如何落地整洁架构?" —— 按层划分模块、依赖倒置
#6.2 级别差异
| 级别 | 期望回答 |
|---|---|
| P5 | 能说出整洁架构的四层结构 |
| P6 | 能解释依赖方向原则,知道如何实现依赖倒置 |
| P7 | 有实际落地经验,能指导团队实施整洁架构 |