命令模式:在复杂业务中解耦“屎山”代码的架构实践
在 Java 开发中命令模式Command Pattern的核心价值在于解耦请求发送者Invoker与请求接收者Receiver并将请求封装为对象。这使得我们可以轻松实现撤销/重做、事务日志、宏命令组合命令、异步处理、流量削峰等高级功能。在经典的DAO - Service - Controller三层架构中命令模式通常作为Service 层的增强设计或独立的任务调度层存在用于处理那些“操作复杂、需要事务控制、可能需要重试或审计”的业务场景。所以本文主要案例实践剖析命令模式Command Pattern如何应用复杂业务已解决“屎山”堆砌带来的技术负债。一、核心设计理念在三层架构中的位置层级传统做法引入命令模式后的做法Controller直接调用service.doSomething()创建Command对象调用commandExecutor.execute(command)Service包含大量if-else分支处理不同业务抽象出Command接口每个具体业务逻辑成为一个独立的 Command 类Service 退化为命令的执行器或协调者DAO被 Service 调用保持不变被 Command 内部调用以完成数据持久化设计图解ControllerhandleRequest()CommandExecutorexecute(Command cmd)undo(Command cmd)«interface»Commandexecute()undo()ConcreteCommandA-receiver: ServiceAexecute()undo()ServiceAdoBusinessLogic()DAOsave()update()二、复杂业务案例银行核心系统的“分布式转账与冲正”系统1. 业务背景在银行或支付系统中转账不仅仅是“扣钱 加钱”。它涉及多重校验余额、风控、黑名单、限额。多系统交互核心账务系统、积分系统、通知系统、反洗钱系统。高可靠性要求必须支持原子性要么全成功要么全失败。异常恢复如果第二步失败必须自动执行**冲正Undo**操作回滚第一步。审计留痕所有操作必须记录日志支持人工或自动重放。这是一个典型的适合使用命令模式的场景因为我们需要将“转账”这个动作封装成一个对象以便在执行失败时能方便地调用其undo()方法或者将其存入数据库队列进行异步重试。2. 架构设计(1) 定义命令接口 (Command)这是模式的核心定义执行和撤销操作。publicinterfaceCommand{/** * 执行命令 * throws BusinessException 业务异常时抛出触发回滚 */voidexecute()throwsBusinessException;/** * 撤销命令冲正 * 当 execute 失败时调用或者用于人工冲正 */voidundo();/** * 获取命令的唯一标识用于日志追踪 */StringgetTraceId();/** * 获取命令类型用于审计 */StringgetCommandType();}(2) 具体命令实现 (ConcreteCommand)以“跨行转账”为例该命令封装了扣款、入账、发通知等逻辑并定义了如何回滚。ComponentScope(prototype)// 重要命令对象通常是有状态的需原型模式publicclassTransferCommandimplementsCommand{privatefinalStringtraceId;privatefinalLongfromAccountId;privatefinalLongtoAccountId;privatefinalBigDecimalamount;// 依赖注入业务服务ReceiverAutowiredprivateAccountServiceaccountService;AutowiredprivateNotificationServicenotificationService;AutowiredprivateRiskControlServiceriskControlService;// 状态标记记录哪些步骤已执行以便精准回滚privatebooleandebitExecutedfalse;privatebooleancreditExecutedfalse;privatebooleannotifiedfalse;publicTransferCommand(StringtraceId,LongfromAccountId,LongtoAccountId,BigDecimalamount){this.traceIdtraceId;this.fromAccountIdfromAccountId;this.toAccountIdtoAccountId;this.amountamount;}Overridepublicvoidexecute()throwsBusinessException{try{// 1. 风控校验riskControlService.check(traceId,fromAccountId,amount);// 2. 扣款 (付款方)accountService.debit(fromAccountId,amount);debitExecutedtrue;// 3. 入账 (收款方)accountService.credit(toAccountId,amount);creditExecutedtrue;// 4. 发送通知notificationService.sendTransferSuccess(fromAccountId,toAccountId,amount);notifiedtrue;System.out.println([命令] 转账成功: traceId);}catch(Exceptione){// 发生任何异常立即触发本地回滚补偿// 注意这里通常配合外层的事务管理器或手动补偿机制undo();thrownewBusinessException(转账执行失败已触发冲正,e);}}Overridepublicvoidundo(){System.out.println([命令] 开始冲正操作: traceId);// 逆序回滚已执行的步骤if(notified){// 通知无法物理撤回但可以发送一条“交易取消”的通知notificationService.sendTransferCancelled(fromAccountId,toAccountId,amount);}if(creditExecuted){// 回滚入账try{accountService.reverseCredit(toAccountId,amount);System.out.println([命令] 已回滚入账);}catch(Exceptione){// 记录严重错误可能需要人工介入System.err.println([命令] 回滚入账失败需人工干预: e.getMessage());}}if(debitExecuted){// 回滚扣款try{accountService.reverseDebit(fromAccountId,amount);System.out.println([命令] 已回滚扣款);}catch(Exceptione){System.err.println([命令] 回滚扣款失败需人工干预: e.getMessage());}}}OverridepublicStringgetTraceId(){returntraceId;}OverridepublicStringgetCommandType(){returnTRANSFER;}}(3) 命令执行器 (CommandExecutor)这是 Invoker负责管理命令的生命周期、事务边界、日志记录和重试。ServicepublicclassCommandExecutor{AutowiredprivateCommandLogDaocommandLogDao;// 持久化命令日志/** * 同步执行命令带事务和日志 */Transactional(rollbackForException.class)publicvoidexecuteSync(Commandcommand){// 1. 记录命令开始日志logCommand(command,STARTED);try{// 2. 执行业务逻辑command.execute();// 3. 记录成功日志logCommand(command,SUCCESS);}catch(BusinessExceptione){// 4. 记录失败日志undo 已在 command 内部调用或在此处统一调用logCommand(command,FAILED: e.getMessage());// 抛出异常让 Spring 事务回滚数据库操作throwe;}}/** * 异步执行命令放入队列用于削峰或解耦 */publicvoidexecuteAsync(Commandcommand){// 先持久化命令状态为 PENDINGlogCommand(command,PENDING);// 提交到线程池或消息队列CompletableFuture.runAsync(()-{try{// 在异步线程中开启新事务executeSync(command);}catch(Exceptione){// 异步失败处理加入重试队列或报警handleAsyncFailure(command,e);}});}privatevoidlogCommand(Commandcmd,Stringstatus){CommandLoglognewCommandLog();log.setTraceId(cmd.getTraceId());log.setType(cmd.getCommandType());log.setStatus(status);log.setCreateTime(LocalDateTime.now());commandLogDao.insert(log);}privatevoidhandleAsyncFailure(Commandcmd,Exceptione){// 策略加入死信队列等待人工处理或定时任务重试System.err.println(异步命令失败进入死信队列: cmd.getTraceId());}}(4) Controller 层调用Controller 变得非常轻量只负责构建命令并委托给执行器。RestControllerRequestMapping(/api/transfer)publicclassTransferController{AutowiredprivateCommandExecutorcommandExecutor;// 工厂或服务用于创建命令对象AutowiredprivateCommandFactorycommandFactory;PostMappingpublicResultVoidtransfer(RequestBodyTransferDTOdto){StringtraceIdUUID.randomUUID().toString();// 1. 创建具体的命令对象CommandcommandcommandFactory.createCommand(TRANSFER,traceId,dto);// 2. 委托执行器执行// 可以选择同步或异步commandExecutor.executeSync(command);returnResult.success(交易已受理流水号traceId);}}三、这种设计的核心优势1. 完美的“撤销/冲正”机制在传统 Service 写法中如果第 3 步失败你需要在第 3 步的catch块里写代码去调用第 2 步和第 1 步的回滚方法代码耦合且容易遗漏。命令模式将“执行”和“撤销”封装在同一个对象内。execute()失败自动调用undo()逻辑内聚且undo()可以设计得非常健壮如记录每一步的状态标志位。2. 支持宏命令组合模式如果业务变成“批量转账”你可以创建一个MacroCommand里面包含 100 个TransferCommand。publicclassMacroCommandimplementsCommand{privateListCommandcommandsnewArrayList();// addCommand...publicvoidexecute(){for(Commandc:commands)c.execute();}publicvoidundo(){// 逆序撤销for(inticommands.size()-1;i0;i--)commands.get(i).undo();}}这对财务日终批处理非常有用。3. 易于实现“命令日志”和“故障恢复”由于命令是对象你可以轻松地将命令对象序列化JSON并存入数据库。场景系统宕机重启后扫描数据库中状态为PENDING或FAILED的命令记录反序列化成对象重新调用execute()。这是实现最终一致性和可靠消息投递的经典手段。4. 解耦与扩展Controller不需要知道具体的业务逻辑细节只需要知道有个CommandExecutor。新增一种业务如“理财购买”只需新增一个BuyWealthCommand类无需修改现有的 Controller 或 Executor 代码符合开闭原则。5. 流量削峰通过executeAsync方法可以将突发的交易请求放入内存队列或数据库队列后端消费者按自己的能力匀速处理保护数据库不被打挂。四、与策略模式的对比特性命令模式 (Command)策略模式 (Strategy)意图封装请求支持撤销、日志、队列封装算法支持运行时切换算法核心方法execute(),undo()execute()(通常无 undo)状态命令对象通常持有请求参数和执行状态策略对象通常无状态或仅持有配置生命周期一次请求对应一个命令实例 (Prototype)策略实例通常单例被多次复用适用场景转账、订单创建、任务调度、宏操作支付方式选择、排序算法、折扣计算在上面的银行案例中因为需要记录“谁在什么时候转了多少钱”以及“失败了怎么退回去”所以必须用命令模式。如果是单纯的“根据用户等级计算手续费”则用策略模式。五、总结在 DAO-Service-Controller 架构中引入命令模式位置在 Service 层之上构建一层“命令层”或在 Service 内部使用命令对象编排复杂逻辑。核心价值解决长流程事务、补偿机制Undo、操作审计和异步解耦问题。适用场景金融交易、电商下单、工作流引擎、复杂的表单提交等对数据一致性和可追溯性要求极高的业务。通过这种设计你的系统将从简单的“增删改查”进化为具备自愈能力和高可维护性的企业级应用。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2433348.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!