一、DDD与微服务为什么是天作之合

咱们先打个比方,DDD(领域驱动设计)就像城市规划师,把城市划分成商业区、住宅区、工业区;而微服务就是各个功能完善的社区,每个社区自己发电、自己供水。这俩凑一块儿,简直就是数字世界的黄金搭档。

举个电商系统的例子。用DDD划分出"订单"、"支付"、"库存"三个限界上下文,对应到微服务就是三个独立服务:

// 订单服务 (Java技术栈示例)
public class OrderService {
    // 创建订单领域逻辑
    public Order createOrder(Long userId, List<Item> items) {
        // 验证用户
        // 计算总价
        // 生成订单号
        // 持久化订单
    }
    
    // 领域事件发布
    @Transactional
    public void confirmOrder(Long orderId) {
        Order order = repository.findById(orderId);
        order.confirm();
        eventPublisher.publish(new OrderConfirmedEvent(order));
    }
}
// 库存服务
public class StockService {
    // 库存扣减逻辑
    @Transactional 
    public void deductStock(String sku, int quantity) {
        // 检查库存
        // 乐观锁更新
        // 记录变更日志
    }
}

这种组合的优势很明显:

  1. 业务边界清晰,每个服务专注自己的"一亩三分地"
  2. 团队可以按照领域划分,沟通成本直线下降
  3. 技术栈可以差异化,比如用Java写订单服务,用Golang写高并发的库存服务

二、分布式事务这个"拦路虎"

微服务拆分后最头疼的就是事务问题。想象一下:用户下单后要同时扣库存、生成订单、创建支付单,这三个操作分布在不同的服务里,怎么保证要么全成功要么全失败?

2.1 常见解决方案比武

先看张对比表:

方案 一致性 性能 复杂度 适用场景
2PC 强一致 银行转账等金融场景
TCC 最终 电商订单等高并发
本地消息表 最终 日志处理等
Saga 最终 长流程业务

2.2 TCC模式实战示例

用Java+Spring Cloud实现一个下单的TCC案例:

// 订单服务TCC接口
public interface OrderTccService {
    @Transactional
    @PostMapping("/try")
    OrderTccResponse tryCreateOrder(@RequestBody OrderRequest request);
    
    @PostMapping("/confirm")
    boolean confirmCreateOrder(@RequestParam Long orderId);
    
    @PostMapping("/cancel")
    boolean cancelCreateOrder(@RequestParam Long orderId);
}

// 实现类关键代码
@Service
public class OrderTccServiceImpl {
    private Map<Long, OrderTccRecord> tryRecords = new ConcurrentHashMap<>();
    
    public OrderTccResponse tryCreateOrder(OrderRequest request) {
        // 1. 预检查
        if(request.getItems().isEmpty()) {
            throw new IllegalArgumentException("商品不能为空");
        }
        
        // 2. 预留资源(冻结库存等)
        OrderTccRecord record = new OrderTccRecord();
        record.setStatus(TccStatus.TRY);
        record.setOrderId(generateOrderId());
        tryRecords.put(record.getOrderId(), record);
        
        // 3. 返回预操作结果
        return new OrderTccResponse(record.getOrderId());
    }
    
    public boolean confirmCreateOrder(Long orderId) {
        // 获取预操作记录
        OrderTccRecord record = tryRecords.get(orderId);
        if(record == null || record.getStatus() != TccStatus.TRY) {
            return false;
        }
        
        // 执行正式业务逻辑
        Order order = buildOrder(record);
        orderRepository.save(order);
        
        // 更新状态
        record.setStatus(TccStatus.CONFIRM);
        return true;
    }
    
    // cancel方法类似,进行资源释放
}

这个实现有几个关键点:

  1. try阶段只是预留资源,不实际创建订单
  2. confirm/cancel阶段要保证幂等性
  3. 需要定时任务补偿悬挂的操作

三、Saga模式的另类解法

对于长业务流程,比如电商的"下单->支付->发货->确认收货"流程,用TCC就太重了。这时候Saga模式更合适。

3.1 事件驱动的Saga实现

用Spring Cloud Stream + Kafka实现:

// 订单服务事件发布
public class OrderService {
    @Autowired
    private StreamBridge streamBridge;
    
    public void createOrder(Order order) {
        // 保存订单
        orderRepository.save(order);
        
        // 发布订单创建事件
        streamBridge.send("orderCreated-out-0", 
            new OrderCreatedEvent(order.getId(), order.getUserId(), order.getAmount()));
    }
    
    // 补偿方法
    public void cancelOrder(Long orderId) {
        // 订单状态更新为已取消
        // 释放相关资源
    }
}
// 支付服务事件处理
@Slf4j
@Service
public class PaymentListener {
    @Autowired
    private PaymentService paymentService;
    
    @Bean
    public Consumer<OrderCreatedEvent> handleOrderCreated() {
        return event -> {
            try {
                paymentService.createPayment(event.getOrderId(), event.getAmount());
            } catch (Exception e) {
                // 发布支付失败事件
                streamBridge.send("paymentFailed-out-0",
                    new PaymentFailedEvent(event.getOrderId()));
                log.error("支付处理失败", e);
            }
        };
    }
    
    // 补偿事件处理
    @Bean
    public Consumer<DeliveryFailedEvent> handleDeliveryFailed() {
        return event -> {
            paymentService.refund(event.getOrderId());
        };
    }
}

这种模式的要点:

  1. 每个服务处理完自己的逻辑后发布下一个事件
  2. 出现异常时发布补偿事件
  3. 需要实现幂等和防重

四、实战中的避坑指南

4.1 选型决策树

遇到分布式事务问题时,可以这么选:

是否要求强一致?
├── 是 → 考虑2PC或本地事务+MQ事务消息
└── 否 → 业务流程是否长?
    ├── 是 → 考虑Saga模式
    └── 否 → 并发量如何?
        ├── 高 → TCC模式
        └── 低 → 本地消息表

4.2 必须知道的注意事项

  1. 幂等性设计:所有操作都要支持重复执行。比如支付服务的createPayment方法要判断是否已处理过该订单
  2. 空回滚问题:在TCC模式中,try阶段可能因为超时失败,但实际执行成功了,cancel时要做检查
  3. 悬挂控制:confirm/cancel可能比try先到,需要记录try状态
  4. 日志追踪:分布式事务一定要有完整的链路日志,建议用traceId串联

4.3 监控告警不可少

建议监控这些指标:

  • 事务成功率
  • 平均处理时长
  • 补偿事务比例
  • 悬挂事务数量

用Prometheus+Grafana配置个看板,出问题时能快速定位。

五、总结与展望

DDD和微服务配合使用,就像咖啡配奶泡——单独喝也不错,但搭配起来风味更佳。分布式事务虽然麻烦,但现在的解决方案已经比较成熟了。我的建议是:

  1. 简单场景用本地消息表
  2. 一般电商用TCC
  3. 复杂流程上Saga
  4. 金融级强一致才考虑2PC

未来随着Service Mesh等技术的发展,可能会涌现更优雅的解决方案。但无论技术怎么变,理解业务本质才是根本,这也是DDD给我们最大的启示。