复杂业务的解决之道,如何使用“中介者模式(Mediator Pattern)”解决复杂业务场景

前面讲过DDD架构用于解决复杂的服务场景问题,但是DDD领域驱动有个大的问题就是需要开发了解一整套理念,需要一定的入门门槛,而是四层架构设计一定会影响开发的进度。基于此,本文提出了"中介者模式(Mediator Pattern)"在特定的复杂业务场景进行使用,是代码更具有健壮性。

在 Java 开发中,中介者模式(Mediator Pattern) 的核心目的是降低多个对象或模块之间的耦合度。它通过引入一个"中介者"对象,让原本需要直接相互交互的对象改为只与中介者交互。

就一般的系统而言,通常都是普通的三层架构,所以本文给出的方案是基于使用中介模式进行优化。对于一个开发者而言,代码的可扩展性和可测试性是直接的目的,不是手段。

DAO - Service - Controller 三层架构中,中介者模式通常不用于简单的 CRUD 流程,而是应用于 Service 层内部跨 Service 的复杂协调场景。当多个业务模块(如订单、库存、积分、优惠券、物流)需要协同完成一个复杂任务,且它们之间存在复杂的网状依赖时,中介者模式是最佳选择。


一、核心设计理念:在三层架构中的位置

层级 传统做法 (网状耦合) 引入中介者模式后的做法
Controller 调用 orderService.create(),可能还需要手动调用 couponServicepointService 只调用 OrderMediator.execute(),将所有复杂性封装在中介者内部
Service OrderService 直接依赖 InventoryService, CouponService, PointService... 导致循环依赖和"上帝类" 各 Service 只关注自身核心逻辑 ,不再直接调用其他兄弟 Service。 Mediator 类 负责编排调用顺序、处理事务边界、协调异常回滚
DAO 被各自的 Service 调用 保持不变

设计图解:
OrderController
+createOrder()
OrderMediator
-orderService
-inventoryService
-couponService
-pointService
-riskService
+executeCreateOrder(Context ctx)
OrderService
+createOrder()
InventoryService
+lockStock()
CouponService
+useCoupon()
PointService
+deductPoints()
RiskService
+checkRisk()
OrderService-.-OrderMediator
通知状态变更
InventoryService-.-OrderMediator
通知库存不足


二、复杂业务案例:电商大促"秒杀下单"全链路协调系统

1. 业务背景

在"双11"等大促场景下,用户点击"立即购买"后,系统需要在毫秒级时间内完成以下一系列强一致性操作:

  1. 风控校验:检查用户是否黑名单、是否刷单。
  2. 优惠券核销:锁定并冻结用户使用的优惠券。
  3. 库存扣减:扣减 SKU 库存(防止超卖)。
  4. 积分抵扣:扣除用户积分。
  5. 订单创建:生成订单主表和明细表。
  6. 消息通知:发送下单成功消息给 MQ,触发后续发货流程。

痛点(网状耦合)

如果不使用中介者,OrderService 可能会直接调用 RiskService.check(), CouponService.use(), InventoryService.deduct()

  • 问题 1OrderService 变得极其臃肿,违反了单一职责原则。
  • 问题 2 :如果 CouponService 未来需要调用 UserService 获取等级,而 UserService 又依赖 OrderService 统计消费额,极易产生循环依赖
  • 问题 3 :一旦第 4 步(积分)失败,需要按逆序回滚前 3 步(优惠券、库存、风控),这种补偿逻辑 散落在 OrderServicetry-catch 块中,难以维护且容易出错。
2. 架构设计实现
(1) 定义中介者接口 (OrderMediator)

虽然可以直接用类,但定义接口有助于测试和扩展。

java 复制代码
public interface OrderMediator {
    /**
     * 执行下单全流程协调
     * @param context 下单上下文(包含用户ID、商品ID、优惠券ID等)
     * @return 订单ID
     */
    String executeCreateOrder(OrderContext context);
}
(2) 具体中介者实现 (SeckillOrderMediator)

这是核心类,它持有所有相关 Service 的引用,并编排执行流程。它也是事务的边界控制者。

java 复制代码
@Component
public class SeckillOrderMediator implements OrderMediator {

    // 注入所有参与的同事类(Colleague)
    @Autowired
    private RiskService riskService;
    @Autowired
    private CouponService couponService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PointService pointService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private MessageProducer messageProducer;

    /**
     * 核心协调逻辑
     * 使用 @Transactional 保证本地数据库操作的一致性
     * 对于远程调用或不可回滚操作,需结合 TCC 或 本地消息表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String executeCreateOrder(OrderContext context) {
        System.out.println(">>> [中介者] 开始协调下单流程,订单号生成中...");

        try {
            // 1. 风控校验 (若失败直接抛出异常,事务回滚)
            riskService.checkRisk(context.getUserId(), context.getProductId());
            logStep(context, "RISK_CHECK_PASS");

            // 2. 优惠券核销
            couponService.lockAndUse(context.getUserId(), context.getCouponId());
            logStep(context, "COUPON_USED");

            // 3. 库存扣减 (最关键,需防超卖)
            inventoryService.deductStock(context.getProductId(), context.getCount());
            logStep(context, "STOCK_DEDUCTED");

            // 4. 积分抵扣
            if (context.getPointsToDeduct() > 0) {
                pointService.deductPoints(context.getUserId(), context.getPointsToDeduct());
                logStep(context, "POINTS_DEDUCTED");
            }

            // 5. 创建订单 (最后一步,依赖前面所有资源已锁定)
            String orderId = orderService.createOrder(context);
            context.setOrderId(orderId);
            logStep(context, "ORDER_CREATED");

            // 6. 发送异步消息 (通常不建议在事务内发MQ,可用事务监听器或本地消息表)
            // 这里为了演示简单,假设使用 Spring 的事务同步管理器
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    messageProducer.sendOrderCreatedEvent(orderId);
                    System.out.println(">>> [中介者] 发送MQ消息成功");
                }
            });

            System.out.println(">>> [中介者] 下单流程全部成功");
            return orderId;

        } catch (Exception e) {
            // 7. 统一异常处理与补偿 (如果需要手动补偿而非依赖数据库回滚)
            System.err.println(">>> [中介者] 流程失败: " + e.getMessage());
            handleCompensation(context, e);
            throw e; // 触发 @Transactional 回滚
        }
    }

    /**
     * 补偿逻辑示例
     * 在分布式场景下,如果某些操作(如调用外部积分系统)无法通过 DB 事务回滚,
     * 则需在此处编写补偿代码(如调用 couponService.rollback())
     */
    private void handleCompensation(OrderContext context, Exception e) {
        // 记录错误日志到补偿表,由定时任务重试
        // 或者立即执行反向操作
        if (e instanceof BusinessException) {
             // 业务异常通常不需要额外补偿,DB 事务会自动回滚
        } else {
             // 系统异常可能需要记录人工介入
        }
    }

    private void logStep(OrderContext context, String step) {
        // 记录流程日志,便于追踪
    }
}
(3) 同事类 (Colleague Services)

这些 Service 变得非常纯粹,只关注自己的领域逻辑,不再知道其他 Service 的存在

优惠券服务 (CouponService)

java 复制代码
@Service
public class CouponService {
    @Autowired
    private CouponDao couponDao;

    public void lockAndUse(Long userId, Long couponId) {
        // 只关心优惠券表的更新
        // 不再调用 orderService 或 inventoryService
        couponDao.updateStatus(couponId, "USED");
        System.out.println("[CouponService] 优惠券已核销: " + couponId);
    }
    
    // 如果需要补偿
    public void rollbackUse(Long couponId) {
        couponDao.updateStatus(couponId, "UNUSED");
    }
}

库存服务 (InventoryService)

java 复制代码
@Service
public class InventoryService {
    @Autowired
    private StockDao stockDao;

    public void deductStock(Long productId, int count) {
        // 只关心库存表的原子更新
        int affected = stockDao.deduct(productId, count);
        if (affected == 0) {
            throw new BusinessException("库存不足");
        }
        System.out.println("[InventoryService] 库存已扣减: " + productId);
    }
}

(其他 Service 类似,不再展示)

(4) Controller 层调用

Controller 只需要面对唯一的入口:中介者。

java 复制代码
@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderMediator orderMediator; // 依赖抽象接口

    @PostMapping("/seckill")
    public Result<String> createSeckillOrder(@RequestBody OrderDTO dto) {
        OrderContext context = convertToContext(dto);
        
        try {
            // 将所有复杂性委托给中介者
            String orderId = orderMediator.executeCreateOrder(context);
            return Result.success("下单成功", orderId);
        } catch (BusinessException e) {
            return Result.fail("下单失败: " + e.getMessage());
        }
    }
}

三、这种设计的核心优势

1. 彻底解耦(网状 -> 星状)
  • 之前 :N 个 Service 互相依赖,复杂度是 O(N2)O(N^2)O(N2)。增加一个新服务(如"保险服务"),可能需要修改 N 个现有 Service。
  • 现在 :所有 Service 只依赖 Mediator,复杂度降为 O(N)O(N)O(N)。新增"保险服务"只需在 Mediator 中增加一行调用代码,现有 Service 无需任何改动。
2. 集中控制流程与事务

复杂的业务流程(如先扣券还是先扣库存)的控制权集中在 SeckillOrderMediator 中。

  • 如果需要调整顺序(例如:先查库存再查风控),只需修改 Mediator 中的一行代码。
  • 事务边界清晰,@Transactional 注解在 Mediator 上,确保所有数据库操作要么全成功,要么全回滚。
3. 统一的异常处理与补偿

在分散的调用中,每个 Service 都要自己处理部分失败的情况。而在中介者模式中,try-catch 块位于 Mediator 内部,可以统一决定:

  • 是直接抛出异常回滚?
  • 还是记录日志进入补偿队列?
  • 还是触发降级策略?
    这使得可靠性设计变得系统化。
4. 易于测试
  • 可以单独 Mock 各个 Service 来测试 Mediator 的流程编排逻辑。
  • 可以单独测试某个 Service 的核心逻辑,而不需要启动整个链路。
5. 符合单一职责原则 (SRP)
  • OrderService 只负责"创建订单数据"。
  • InventoryService 只负责"管理库存"。
  • SeckillOrderMediator 只负责"协调下单流程"。
    各司其职,代码可维护性极大提升。

四、注意事项与潜在陷阱

  1. 中介者膨胀(God Object)

    如果 Mediator 类中注入了太多 Service,或者逻辑过于复杂,它本身可能变成一个"上帝类"。

    • 对策 :如果流程过于复杂,可以将 Mediator 拆分为多个子中介者(如 PaymentMediator, FulfillmentMediator),或者将 Mediator 内部的步骤提取为独立的 Command 对象(结合命令模式)。
  2. 性能瓶颈

    所有请求都经过 Mediator,如果 Mediator 内部逻辑有阻塞操作,会影响整体吞吐量。

    • 对策 :在 Mediator 内部合理使用异步调用(CompletableFuture)或消息队列,将非核心路径(如发送通知、积分赠送)异步化。
  3. 分布式事务问题

    上述案例假设所有 Service 在同一个数据库中,使用本地事务即可。如果 Service 分布在不同的微服务(不同数据库)中,@Transactional 将失效。

    • 对策 :此时 Mediator 的角色演变为 Saga 编排器(Saga Orchestrator)。它需要记录每一步的状态,并在失败时调用各服务的补偿接口(TCC 模式或 Saga 模式),而不仅仅依赖数据库回滚。

五、总结

在 DAO-Service-Controller 架构中,中介者模式 是解决 Service 层复杂协作 的一个重要的一种设计模式。是解决屎山代码的有效方式之一。

  • 适用场景:涉及多个领域模型协同的复杂业务(如开户、下单、理赔、审批流)。
  • 核心价值:将"网状依赖"转化为"星状依赖",将"分散的流程控制"集中化,将"复杂的异常处理"统一化。
  • 设计关键 :创建一个专门的 Mediator 类(或 Service),注入所有相关的子 Service,由它来编排执行顺序、控制事务边界和处理异常补偿,让子 Service 回归纯粹的领域逻辑。

通过引入中介者,你的系统将从"牵一发而动全身"的脆弱结构,转变为"高内聚、低耦合"的健壮架构。

相关推荐
Predestination王瀞潞4 小时前
4.3.2 存储->微软文件系统标准(微软,自有技术标准):NTFS(New Technology File System)新技术文件系统
linux·microsoft·ntfs
柯儿的天空4 小时前
WebGPU全面解析:新一代Web图形与计算API
前端·chrome·microsoft·前端框架·chrome devtools·view design
符哥200818 小时前
Firebase quickstart-android 各模块功能深度补充详解
microsoft
Sharewinfo_BJ1 天前
拒绝“盲人摸象”!打破数据孤岛,重塑零售决策力
microsoft·零售
Azure DevOps1 天前
Azure DevOps:应用远程MCP服务器,提升工作效率
服务器·microsoft·flask·azure·devops
SSONICX1 天前
ESP32:6.ADC
microsoft
迅易科技1 天前
在 Azure 容器化部署 OpenClaw:从零到生产环境实战指南
microsoft·flask·azure
步步为营DotNet1 天前
深入剖析.NET 11中Microsoft.Extensions.AI的应用与优化 前言
人工智能·microsoft·.net
05大叔1 天前
AI智能伴侣项目
人工智能·microsoft