Mock到底Mock谁?Clean Architecture 实战+Mock测试最佳实践(含对比代码)

Clean Architecture 与传统三层架构对比实战:从创建订单功能看架构演进之道

作者:killian| 更新日期:2025年6月 | 字数:约3200字

在企业级开发中,架构往往决定了项目的可维护性、可测试性和可演化性。今天我们将以“创建订单”功能为例,深入对比两种常见的后端架构:传统三层架构与 Clean Architecture,并结合 MyBatis-Plus 持久化实现 + Mockito 单元测试场景,揭示两者的实用差异。


一、引言:三层架构的黄金时代与Clean风潮的兴起

长期以来,Java后端开发主要基于“控制层Controller → 业务层Service → 数据层Repository”的三层架构。它逻辑清晰、便于分工,在早期系统中十分高效。

然而,随着业务复杂度提升,三层架构面临“Service臃肿化”“业务逻辑难以复用”“测试不友好”等痛点。为了解决这些问题,Clean Architecture(整洁架构)开始在微服务时代快速传播。

Clean Architecture 强调“依赖倒置”,鼓励用例驱动、接口编程、解耦耦合,同时提供了高度可测试性的天然设计。


二、业务背景:创建订单的典型流程

以“用户提交购物车商品 → 创建订单记录”这一过程为例,包含:

  • 接收订单请求(userId, 商品列表)
  • 验证数据是否为空
  • 构建订单对象
  • 保存到数据库

我们将通过代码对比这两种架构如何实现这一流程。


三、传统三层架构实现:简单直接,渐臃肿

架构结构

com.example.layered
├── controller
├── service / impl
├── mapper
├── entity
├── dto

☑️ 核心代码片段

Controller 层
@RestController
@RequestMapping("/orders")
public class OrderController {
    @PostMapping
    public ResponseEntity<Void> create(@RequestBody CreateOrderRequestDTO dto) {
        orderService.createOrder(dto);
        return ResponseEntity.ok().build();
    }
}
Service 层
public void createOrder(CreateOrderRequestDTO dto) {
    if (dto.getItems() == null || dto.getItems().isEmpty()) {
        throw new IllegalArgumentException("商品不能为空");
    }
    OrderEntity entity = new OrderEntity();
    entity.setUserId(dto.getUserId());
    entity.setItems(dto.getItems());
    orderMapper.insert(entity);
}

✅ 优点

  • 快速交付
  • 思路清晰

❌ 缺点

  • Service 层容易“变胖”:逻辑、构建、持久化全在一起
  • 没有明确的“业务模型”
  • 依赖 MyBatis 实现,难以 Mock 测试

四、Clean Architecture 实现:解耦清晰,测试友好

架构结构

com.example.clean
├── adapter.in.web                # Controller
├── application.port.in           # 用例接口
├── application.service           # 用例实现
├── domain.model                  # 领域模型
├── domain.port.out               # Repository接口
├── adapter.out.persistence       # MyBatis实现

☑️ 核心代码片段

Controller 层
@RestController
@RequestMapping("/orders")
public class OrderController {
    @PostMapping
    public ResponseEntity<Void> create(@RequestBody CreateOrderRequestDTO dto) {
        createOrderUseCase.create(dto);
        return ResponseEntity.ok().build();
    }
}
UseCase 实现层
public void create(CreateOrderRequestDTO dto) {
    Order order = new Order(dto.getUserId(), dto.getItems());
    order.validate();
    orderRepository.save(order);
}
Repository 接口
public interface OrderRepository {
    void save(Order order);
}
Repository 实现
public void save(Order order) {
    OrderEntity entity = new OrderEntity();
    entity.setUserId(order.getUserId());
    entity.setItems(order.getItems());
    orderMapper.insert(entity);
}

✅ 优点

  • 每一层职责单一、清晰
  • 可完全Mock OrderRepository,测试 CreateOrderService
  • Order 对象脱离 ORM,更易维护和复用

❌ 缺点

  • 初期结构复杂(入门门槛略高)

五、Mock 测试的差异:可维护性的真正分水岭

✅ 三层架构测试:只能Mock MyBatis Mapper

@Mock OrderMapper orderMapper;
@InjectMocks OrderServiceImpl orderService;

@Test
void should_save_order() {
    CreateOrderRequestDTO dto = new CreateOrderRequestDTO(...);
    orderService.createOrder(dto);
    verify(orderMapper).insert(any());
}

缺点:你只能测试“是否执行”,无法验证“业务是否合理”。

✅ Clean 架构测试:可捕获并验证业务模型

@Mock OrderRepository orderRepository;
@InjectMocks CreateOrderService service;

@Test
void should_construct_domain_model_correctly() {
    CreateOrderRequestDTO dto = ...;
    service.create(dto);
    ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
    verify(orderRepository).save(captor.capture());
    assertEquals("userX", captor.getValue().getUserId());
}

优势:验证构建出的领域对象是否符合预期逻辑,这是真正的单元测试价值。


六、总结对比:实战视角下的取舍与进化

项目属性 三层架构 Clean Architecture
上手速度 ⭐⭐⭐⭐ ⭐⭐
架构清晰度 ⭐⭐ ⭐⭐⭐⭐
单元测试友好度 ⭐⭐ ⭐⭐⭐⭐⭐
对外依赖隔离 ⭐⭐⭐⭐
可维护性 ⭐⭐ ⭐⭐⭐⭐
团队协作支持 ⭐⭐ ⭐⭐⭐⭐

七、适用建议与进化建议

✅ 适合三层架构的场景:

  • 小型服务 / MVP 快速开发
  • 业务逻辑不复杂的管理后台

✅ 适合 Clean Architecture 的场景:

  • 需要长期演进、可维护性强的核心业务
  • 涉及多个外部依赖(数据库、消息队列、API)
  • 测试覆盖要求高的中大型项目

进化路线建议

如果你项目是三层架构,也不必立刻大改。不妨:

  • 先从提取用例逻辑开始(UseCase)
  • 将复杂逻辑迁移到 Domain 层
  • 用接口(Port)抽象对外依赖

最终,自然演进为 Clean 架构。


结语:架构不是目的,清晰才是力量

架构的本质不是“炫技”,而是“让系统更清晰、更可控、更能测试”。Clean Architecture 提供了一种思维方式,它引导我们把业务当作核心,而非框架和数据库

如果你还在三层架构中挣扎,不妨试一次 Clean Architecture。就像我们在“创建订单”功能中看到的,你会发现:逻辑分得清、测试写得动、代码能长久

未来,我们还将探讨 Event Sourcing、CQRS 与 Clean 的融合路径,欢迎关注并交流。

本文由 @killian 原创,转载请注明出处。
☕ 请作者喝杯咖啡,持续更新更深入的干货

彩蛋时间:如果你看到了这里,说明你是那种喜欢动手实战的人。那我悄悄分享一个开发圈流传的工具试用入口,貌似跟高效调试很有关系,地址也挺特别的:

入口

据说注册还能解锁一些隐藏功能,懂的都懂(别外传 )

你可能感兴趣的:(程序员的思维乐园,Mock)