DDD-018:应用服务与事务脚本

发布时间:2026/6/8 22:26:15
DDD-018:应用服务与事务脚本
DDD-018:应用服务与事务脚本18.1 应用服务概述18.1.1 什么是应用服务?【原理】应用服务(Application Service)是 DDD 分层架构中的应用层核心组件,负责协调领域对象完成业务用例。它是外部世界与领域模型之间的桥梁。应用服务的核心职责:用例协调:编排领域对象完成完整的业务流程事务管理:确保用例执行的原子性权限检查:验证用户是否有执行权限数据转换:将外部请求转换为领域对象可理解的命令应用服务的特点:薄层:不包含业务规则,只做协调无状态:不持有业务数据事务边界:每个方法对应一个事务用例导向:方法命名反映业务用例18.1.2 应用服务与领域服务的区别【对比分析】方面应用服务领域服务位置应用层领域层职责协调用例执行封装跨对象业务逻辑业务规则不包含包含事务管理负责不涉及依赖依赖多个领域服务和仓储只依赖领域对象可测试性需要Mock基础设施纯单元测试命名动词短语(createOrder)动词短语(transfer)示例对比:// ========== 应用服务:协调多个领域对象 ==========@ServicepublicclassOrderApplicationService{privatefinalOrderRepositoryorderRepository;privatefinalProductRepositoryproductRepository;privatefinalInventoryServiceinventoryService;privatefinalEventPublishereventPublisher;@TransactionalpublicOrderIdcreateOrder(CreateOrderCommandcommand){// 1. 获取数据(协调)ListProductproducts=productRepository.findByIds(...);// 2. 调用领域逻辑(委托)Orderorder=Order.create(command,products);// 3. 调用外部服务(协调)inventoryService.reserveStock(...);// 4. 持久化(协调)orderRepository.save(order);// 5. 发布事件(协调)eventPublisher.publish(order.domainEvents());returnorder.getId();}}// ========== 领域服务:封装业务逻辑 ==========publicclassTransferService{publicvoidtransfer(Accountfrom,Accountto,Moneyamount){// 纯业务逻辑,无基础设施依赖from.debit(amount);to.credit(amount);}}18.2 事务脚本的问题18.2.1 什么是事务脚本?【历史架构问题】事务脚本(Transaction Script)是一种以过程化方式组织业务逻辑的模式,将所有业务逻辑放在一个方法中,按照步骤依次执行。事务脚本的特点:所有逻辑在一个方法中直接操作数据面向过程思维业务规则散落在各处18.2.2 事务脚本的典型实现【错误示例】// ❌ 典型的事务脚本实现@ServicepublicclassOrderService{@AutowiredprivateOrderDaoorderDao;@AutowiredprivateOrderItemDaoorderItemDao;@AutowiredprivateProductDaoproductDao;@AutowiredprivateUserDaouserDao;@AutowiredprivateCouponDaocouponDao;@AutowiredprivateInventoryDaoinventoryDao;@AutowiredprivatePointDaopointDao;@AutowiredprivateNotificationServicenotificationService;@TransactionalpublicLongcreateOrder(CreateOrderDTOdto){// ===== 第一步:验证用户 =====Useruser=userDao.findById(dto.getUserId());if(user==null){thrownewBusinessException("用户不存在");}if(user.getStatus()!=1){thrownewBusinessException("用户已被禁用");}// ===== 第二步:验证商品 =====ListProductproducts=productDao.findByIds(dto.getProductIds());if(products.size()!=dto.getProductIds().size()){thrownewBusinessException("部分商品不存在");}for(Productproduct:products){if(product.getStatus()!=1){thrownewBusinessException("商品已下架: "+product.getName());}if(product.getStock()=0){thrownewBusinessException("商品库存不足: "+product.getName());}}// ===== 第三步:计算价格 =====BigDecimaltotalAmount=BigDecimal.ZERO;MapLong,Integerquantities=dto.getQuantities();for(Productproduct:products){Integerquantity=quantities.get(product.getId());BigDecimalitemAmount=product.getPrice().multiply(newBigDecimal(quantity));totalAmount=totalAmount.add(itemAmount);}// ===== 第四步:应用优惠券 =====Couponcoupon=null;if(dto.getCouponCode()!=null){coupon=couponDao.findByCode(dto.getCouponCode());if(coupon==null){thrownewBusinessException("优惠券不存在");}if(coupon.getStatus()!=1){thrownewBusinessException("优惠券已失效");}if(coupon.getExpireTime().before(newDate())){thrownewBusinessException("优惠券已过期");}if(coupon.getMinAmount().compareTo(totalAmount)0){thrownewBusinessException("订单金额不满足优惠券使用条件");}// 计算优惠金额if("FIXED".equals(coupon.getType())){totalAmount=totalAmount.subtract(coupon.getAmount());}elseif("PERCENT".equals(coupon.getType())){BigDecimaldiscount=totalAmount.multiply(coupon.getPercent()).divide(newBigDecimal(100));totalAmount=totalAmount.subtract(discount);}}// ===== 第五步:扣减库存 =====for(Productproduct:products){Integerquantity=quantities.get(product.getId());intaffected=inventoryDao.decreaseStock(product.getId(),quantity);if(affected==0){thrownewBusinessException("库存扣减失败");}}// ===== 第六步:创建订单 =====Orderorder=newOrder();order.setUserId(user.getId());order.setStatus("CREATED");order.setTotalAmount(totalAmount);order.setCreateTime(newDate());orderDao.insert(order);// ===== 第七步:创建订单项 =====for(Productproduct:products){Integerquantity=quantities.get(product.getId());OrderItemitem=newOrderItem();item.setOrderId(order.getId());item.setProductId(product.getId());item.setProductName(product.getName());item.setPrice(product.getPrice());item.setQuantity(quantity);item.setSubtotal(product.getPrice().multiply(newBigDecimal(quantity)));orderItemDao.insert(item);}// ===== 第八步:标记优惠券已使用 =====if(coupon!=null){couponDao.markUsed(coupon.getId(),order.getId());}// ===== 第九步:扣减积分 =====if(dto.getUsePoints()!=nulldto.getUsePoints()0){UserPointuserPoint=pointDao.findByUserId(user.getId());if(userPoint.getPoints()dto.getUsePoints()){thrownewBusinessException("积分不足");}pointDao.decrease(user.getId(),dto.getUsePoints());// 积分抵扣金额BigDecimalpointDiscount=newBigDecimal(dto.getUsePoints()).divide(newBigDecimal(100));totalAmount=totalAmount.subtract(pointDiscount);// 更新订单金额order.setTotalAmount(totalAmount);order.setPointUsed(dto.getUsePoints());orderDao.update(order);}// ===== 第十步:发送通知 =====notificationService.sendOrderCreatedNotification(user.getId(),order.getId());returnorder.getId();}// 其他方法也是类似的事务脚本...@TransactionalpublicvoidpayOrder(LongorderId,PaymentDTOdto){// 又是一个长长的事务脚本...// 验证订单 - 验证支付方式 - 调用支付网关 - 更新订单状态 -// 确认库存 - 增加积分 - 发送通知 - ...}@TransactionalpublicvoidcancelOrder(LongorderId,Stringreason){// 又是一个长长的事务脚本...}}18.2.3 事务脚本的问题分析【根本原因分析】问题具体表现根本原因业务逻辑散落验证规则、计算逻辑到处都是缺乏领域对象封装难以测试测试需要启动整个应用逻辑与基础设施强耦合难以复用相同的验证逻辑重复出现没有提取为领域概念难以维护修改一个规则影响多处修改发散,缺乏内聚职责不清Service包含验证、计算、持久化违反单一职责原则事务过长一个事务包含多个步骤缺乏聚合边界问题示意图:┌─────────────────────────────────────────────────────────────────────────┐ │ OrderService.createOrder() │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │用户验证 │─▶│商品验证 │─▶│价格计算 │─▶│优惠券处理│─▶│库存扣减 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ │ │ DAO │ │ DAO │ │ 计算 │ │ DAO │ │ DAO │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ └────────────┴─────────────────────────┴────────────┘ │ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────▼─────┐ ┌─────────┐ │ │ │订单创建 │─▶│订单项创建│─▶│优惠券标记│─▶│积分扣减 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ │ │ DAO │ │ DAO │ │ DAO │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ ┌─────────┐ │ │ │发送通知 │ │ │ └─────────┘ │ │ │ │ 问题:所有逻辑在一个方法中,难以维护、测试、复用 │ │ │ └─────────────────────────────────────────────────────────────────────────┘18.3 正确的应用服务设计18.3.1 应用服务的职责边界【DDD 如何解决】应用服务应该薄而专注,只做协调工作,不包含业务逻辑。┌─────────────────────────────────────────────────────────────────────────┐ │ 应用服务职责边界 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ✅ 应该做的: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 1. 接收命令/查询对象 │ │ │ │ 2. 验证权限(可委托给权限服务) │ │ │ │ 3. 获取领域对象(通过仓储) │ │ │ │ 4. 调用领域对象的方法(业务逻辑在领域对象内部) │ │ │ │ 5. 持久化领域对象 │ │ │ │ 6. 发布领域事件 │ │ │ │ 7. 返回结果 │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ ❌ 不应该做的: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 1. 业务规则验证(应在领域对象中) │ │ │ │ 2. 业务计算逻辑(应在领域对象中) │ │ │ │ 3. 直接操作数据库(应通过仓储) │ │ │ │ 4. 格式化响应数据(应在DTO转换器中) │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘18.3.2 正确的应用服务实现【正确示例】// ✅ 正确的应用服务实现@Service@RequiredArgsConstructorpublicclassOrderApplicationServiceimplementsOrderUseCase{// 只依赖出站端口privatefinalOrderRepositoryorderRepository;privatefinalProductRepositoryproductRepository;privatefinalCouponRepositorycouponRepository;privatefinalInventoryServiceinventoryService;privatefinalEventPublishereventPublisher;@Override@TransactionalpublicOrderIdcreateOrder(CreateOrderCommandcommand){// 1. 获取商品(协调)ListProductproducts=productRepository.findByIds(command.getItems().stream(