前面讲过DDD架构用于解决复杂的服务场景问题,但是DDD领域驱动有个大的问题就是需要开发了解一整套理念,需要一定的入门门槛,而是四层架构设计一定会影响开发的进度。基于此,本文提出了"中介者模式(Mediator Pattern)"在特定的复杂业务场景进行使用,是代码更具有健壮性。
在 Java 开发中,中介者模式(Mediator Pattern) 的核心目的是降低多个对象或模块之间的耦合度。它通过引入一个"中介者"对象,让原本需要直接相互交互的对象改为只与中介者交互。
就一般的系统而言,通常都是普通的三层架构,所以本文给出的方案是基于使用中介模式进行优化。对于一个开发者而言,代码的可扩展性和可测试性是直接的目的,不是手段。
在 DAO - Service - Controller 三层架构中,中介者模式通常不用于简单的 CRUD 流程,而是应用于 Service 层内部 或 跨 Service 的复杂协调场景。当多个业务模块(如订单、库存、积分、优惠券、物流)需要协同完成一个复杂任务,且它们之间存在复杂的网状依赖时,中介者模式是最佳选择。
一、核心设计理念:在三层架构中的位置
| 层级 | 传统做法 (网状耦合) | 引入中介者模式后的做法 |
|---|---|---|
| Controller | 调用 orderService.create(),可能还需要手动调用 couponService 和 pointService |
只调用 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"等大促场景下,用户点击"立即购买"后,系统需要在毫秒级时间内完成以下一系列强一致性操作:
- 风控校验:检查用户是否黑名单、是否刷单。
- 优惠券核销:锁定并冻结用户使用的优惠券。
- 库存扣减:扣减 SKU 库存(防止超卖)。
- 积分抵扣:扣除用户积分。
- 订单创建:生成订单主表和明细表。
- 消息通知:发送下单成功消息给 MQ,触发后续发货流程。
痛点(网状耦合) :
如果不使用中介者,OrderService 可能会直接调用 RiskService.check(), CouponService.use(), InventoryService.deduct()。
- 问题 1 :
OrderService变得极其臃肿,违反了单一职责原则。 - 问题 2 :如果
CouponService未来需要调用UserService获取等级,而UserService又依赖OrderService统计消费额,极易产生循环依赖。 - 问题 3 :一旦第 4 步(积分)失败,需要按逆序回滚前 3 步(优惠券、库存、风控),这种补偿逻辑 散落在
OrderService的try-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只负责"协调下单流程"。
各司其职,代码可维护性极大提升。
四、注意事项与潜在陷阱
-
中介者膨胀(God Object) :
如果 Mediator 类中注入了太多 Service,或者逻辑过于复杂,它本身可能变成一个"上帝类"。
- 对策 :如果流程过于复杂,可以将 Mediator 拆分为多个子中介者(如
PaymentMediator,FulfillmentMediator),或者将 Mediator 内部的步骤提取为独立的 Command 对象(结合命令模式)。
- 对策 :如果流程过于复杂,可以将 Mediator 拆分为多个子中介者(如
-
性能瓶颈 :
所有请求都经过 Mediator,如果 Mediator 内部逻辑有阻塞操作,会影响整体吞吐量。
- 对策 :在 Mediator 内部合理使用异步调用(
CompletableFuture)或消息队列,将非核心路径(如发送通知、积分赠送)异步化。
- 对策 :在 Mediator 内部合理使用异步调用(
-
分布式事务问题 :
上述案例假设所有 Service 在同一个数据库中,使用本地事务即可。如果 Service 分布在不同的微服务(不同数据库)中,
@Transactional将失效。- 对策 :此时 Mediator 的角色演变为 Saga 编排器(Saga Orchestrator)。它需要记录每一步的状态,并在失败时调用各服务的补偿接口(TCC 模式或 Saga 模式),而不仅仅依赖数据库回滚。
五、总结
在 DAO-Service-Controller 架构中,中介者模式 是解决 Service 层复杂协作 的一个重要的一种设计模式。是解决屎山代码的有效方式之一。
- 适用场景:涉及多个领域模型协同的复杂业务(如开户、下单、理赔、审批流)。
- 核心价值:将"网状依赖"转化为"星状依赖",将"分散的流程控制"集中化,将"复杂的异常处理"统一化。
- 设计关键 :创建一个专门的
Mediator类(或 Service),注入所有相关的子 Service,由它来编排执行顺序、控制事务边界和处理异常补偿,让子 Service 回归纯粹的领域逻辑。
通过引入中介者,你的系统将从"牵一发而动全身"的脆弱结构,转变为"高内聚、低耦合"的健壮架构。