当前位置: 首页 > news >正文

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. 获取数据(协调)List<Product>products=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("用户已被禁用");}// ===== 第二步:验证商品 =====List<Product>products=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;Map<Long,Integer>quantities=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()!=null&&dto.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. 获取商品(协调)List<Product>products=productRepository.findByIds(command.getItems().stream(
http://www.cnnetsun.cn/news/2833021.html

相关文章:

  • 103、飞控仿真环境搭建:Gazebo与PX4 SITL
  • 【Ubuntu】使用ffmpeg解析m3u8网页视频
  • 7大真实任务实测 Opus 4.8、Gemini 3.5 Flash、GPT-5.5、Qwen3.7-Max
  • Spring依赖注入的方式
  • Gemini 3.5 深度实测|碾压前代!多模态+工程协作落地,重新定义AI开发辅助上限
  • 深度解析飞算 JavaAI 智能引导的五大步骤:AI 是如何把一句需求变成工程级 Java 代码的?
  • 洛雪音乐音源配置终极指南:从零搭建专业级音乐库的完整方案
  • 网规笔记真题解析:2024年11月软考网规案例分析
  • 如何让机器人在未知环境中实时构建3D地图?RTAB-Map技术深度解析
  • MyBatis-Plus 性能分析实战
  • nmap:网络扫描祖师爷,二十多年过去还是没对手
  • HsMod:炉石传说玩家的全能工具箱,55项功能重新定义游戏体验
  • ArduPilot自动驾驶系统核心技术架构深度解析
  • 基于S32K144的PMSM无感FOC实战:从原理到MCAT调试全解析
  • Layerscape FTM定时器级联:突破16位限制实现长周期高精度计时
  • 鸣潮智能助手终极指南:3步解放你的游戏时间
  • 光学微操纵用HE11波导与SPP倏逝场光力交互计算工具包
  • 解决: Error while loading conda entry point: anaconda-cloud-auth (No module named ‘typing_extensions‘)
  • connecthomeip/matter 属性读全流程
  • 洛雪音乐音源终极指南:如何构建稳定高效的音乐播放器架构
  • 让电子课本从云端走到桌面:你的个人教学资源管家
  • Adobe Illustrator脚本终极指南:20个免费工具提升设计效率10倍
  • Python与AutoCAD的完美融合:pyautocad如何让CAD编程效率飙升500%
  • 【Gabor神经网络(GNN)】级联多尺度 Gabor 分解架构
  • 轻量级跨语言手写检索技术解析与应用实践
  • 口述编程实战:5分钟从零写出批量文件重命名工具(vibe-coding第一次实操)
  • 告别十六进制编辑:3步掌握暗黑破坏神2可视化存档编辑器
  • 5分钟永久备份QQ空间:GetQzonehistory让你的青春记忆永不丢失
  • 地震预警系统开发技术方案
  • 避坑指南:用VS2017编译OpenCASCADE 7.3.0,解决Qt项目链接失败问题