以前在服务化设计模式实践,里面介绍了交易侧系统服务变迁的模式,服务的变迁更好的支持了业务的发展,伴随着业务的发展,对业务系统内部的要求也更好,须要具备更好的扩展性。随着业务的不断发展,每一个服务内部的逻辑也变得愈来愈多,须要有更好的抽象来支持之后更多的业务类型。算法
重构的项目有订单服务,预订系统,退款系统;这三个系统都是与用户交易行为息息相关。设计模式
其中订单系统参与重构的模块为订单建立,订单状态流转,订单支付;性能优化
预订系统的重构主要为了支撑更多的预订方式,如以前已经支持的库存模式、商家接单模式和售中客服模式,伴随着重构还须要支持商家系统直连模式,并且须要可以支持之后业务发展更多的预订模式。架构
退款服务的复杂度主要来源于多种退款类型,如用户退款,系统退款,商家退款和客服退款等多种类型,而每种类型又有各类不一样的退款规则;退款服务须要支持多种业务,如已有的KTV预订和将要扩展出的酒水点单。并发
在这里咱们主要来说讲预订系统重构,由于这个系统的重构几乎涵盖了订单服务和退款服务重构所使用到的技术异步
抽象预订流程,并模板化分布式
对可变化的部分支持配置化ide
在上线过程当中支持新老流程切换微服务
由图中能够看出,业务流程很是复杂,一个订单的预订过程会根据不一样的状况走不一样的预订渠道,若是一个预订渠道由于某种缘由预订失败了,可能会继续使用另一个预订渠道继续进行预订,也就是会发生流转。高并发
另外,在预订成功和预订失败时,会须要作一些其余操做,例如发送短信告知用户结果等;
图中还有一点没有体现的是,在开始发起预订时,须要校验数据的正确性,校验是否复核预订规则等等校验。
根据这些条件咱们作了如下抽象:
首先订单从预订开始、预订中到预订成功/失败定义为预订的主流程,其中每一个接单都是一个重要的业务节点,这种主流程定义为一级业务。
对于不一样的预订模式(如库存模式、商家接单模式、客服售中介入模式和商家系统直连等),抽象为预订渠道。预订渠道以前的转化定义为渠道流转。
预订渠道会直接影响预订结果
预订中、预订成功/失败 时渠道须要个性化的操做,如商家接单渠道开始时须要通知商家等,这种流程会影响一级业务,但其业务具备个性化特征,所以定义为二级业务。
同时预订中、预订成功/失败后 须要进行不影响业务流程的操做,如发送短信告知用户预订结果,记录一些属性等等。这部分业务定义为三级业务。
一级业务是系统最重要的业务,业务流程标准化且会直接影响业务结果;二级业务是一级业务一个步骤,但由于预订渠道的不一样而有个性化操做;三级业务是根据业务结果来执行的操做,不会再影响系统的主流程。
3.1 核心业务流程
预订中核心业务流程是最重要的部分,也就是图中所标注的一级业务,每个步骤都是一个重要的业务节点,且每个节点都会有一些复杂的逻辑。
所以在重构时,将核心业务流程的实现定义为一个模板引擎,在这个模板引擎中的每个节点均可以是一个接口,能够任意的配置。在代码上的表现就会是这样的。
开始预订:
public class KtvReserveService { public KtvReserveResultDTO reserve(KtvReserveContext reserveContext) throws ReserveException { //校验 KtvValidateResult validateResult = this.ktvReserveValidateStack. validate(reserveContext); if (validateResult == null || !validateResult.isValid()) { return KtvReserveResultDTO.createFailedResult("validate invalid"); } //断定预订渠道 KtvReserveChannel reserveChannel = reserveChannelJudgeService. judgeChannelType(reserveContext); reserveDataService.store() reserveDataService.transferReserveChannelStatus(); //开始渠道预订 ChannelResult channelResult = this.reserveChannelService. reserve(reserveContext); return KtvReserveResultDTO
.genResult(channelResult.isSuccess(), channelResult.getDesc(),
reserveFlow.getReserveId()); }} 渠道反馈预订结果: public class KtvReplyReserveService { @Override public ReplyReserveResult reply(KtvReplyReserveInfo replyReserveInfo) throws ReplyReserveException { //校验 KtvValidateResult validateResult = replyReserve ValidateStack.validate(replyReserveInfo); if (validateResult == null || !validateResult.isValid()) { logger.warn(String.format(" %s validate failed", param)); return ReplyReserveResult.createFailedResult("validate failed"); } //更新预订状态 reserveDataService.transferReserveChannelStatus(); ReplyReserveResult result; //断定预订结果 KtvReserveStatus toReserveStatus = this.reserveChannelJudgeService. judgeReserveResult(replyReserveInfo); boolean reserveFailed = toReserveStatus == null || toReserveStatus == KtvReserveStatus. ReserveFailed || toReserveStatus == KtvReserveStatus.Init; if (reserveFailed) { //预订失败处理 result = this.reserveFailed(replyReserveInfo); } else if (toReserveStatus == KtvReserveStatus.ReserveSuccess) { //预订成功处理 result = this.reserveSuccess(replyReserveInfo); } else { // 须要转移其余渠道预订 result = ktvReserveTransferService.transferChannel(ktvReserveContext); } // 渠道处理内部事务 this.replyReserveChannelService.reply(replyReserveInfo); return result; }}
3.2 校验栈
在业务性很强的服务来讲,在业务开始以前须要有复杂的校验,若是在这个服务中支持多种业务类型,还须要根据不一样的业务类型来选择不一样的校验逻辑,所以在服务中将校验栈独立出来。
校验栈的组装采用责任链模式,这样每一个校验service经过组装的方式便可以灵活支持多种校验。可是对于业务主流程来讲,把校验service的组装服务并不适合放在主业务流程里,所以在重构的时候将校验栈的组装逻辑放在一个单独的service中采用代理模式进行组装。
public interface KtvReserveValidateService { /** * 校验预订信息 * @param reserveContext * @return */ KtvValidateResult validate(KtvReserveContext reserveContext);} public class KtvReserveValidateStack implements KtvReserveValidateService { private List validateServices; public void setValidateServices( List validateServices) { this.validateServices = validateServices; } @Override public KtvValidateResult validate(KtvReserveContext reserveContext) { if (CollectionUtils.isEmpty(validateServices)) return KtvValidateResult.validResut(); for (KtvReserveValidateService service : validateServices) { KtvValidateResult result = service.validate(reserveContext); if (result == null || !result.isValid()) return result; } return KtvValidateResult.validResut(); }}
3.3 业务分级
在前面讲到,在重构中将代码功能分红了一级功能,二级功能和三级功能。
其中一级功能的每一个步骤都须要严格保证,若是发生问题就须要直接影响业务流程,例如在预订业务中,预订数据状态的更新就是一级业务,若是更新失败就须要终结业务;
二级业务也是重要的业务,可是不须要二级业务不能影响最终的业务结果,可是当二级业务出错时也须要及时处理,如在更新订单状态为购买成功时发生错误,须要及时告警,或者异步化保证数据一致性;
三级业务彻底不影响业务流程,不少都是异步化调用外部服务,如短信通知用户、双写订单上的预订状态(老业务)等。预订服务中的三级业务都是根据预订结果而触发,所以在这里使用观察者模式实现便可。
业务服务对业务流程相对较多,并且每一步出现问题都有可能直接影响购买结果,这种与钱息息相关的业务,一单出错就会有各方来追杀,并且也极大影响用户和商家的体验。对业务分级是将不用影响最终结果的业务剥离出去,将最核心的业务重点对待,不一样级别用不一样的处理方式。
3.4 数据模型统一
这里的业务模型是业务流程的数据统一。例如在开始预订的业务中使用ReserveContext做为整个业务流程的数据协议,在分业务时也能采用相同的数据,即避免相同数据的重复读取也便于立减和时间。
一个业务流程的处理,其实也是一种服务的处理过程,而数据模型就是其业务的协议,好协议才能产生好实践。
3.5 机制同策略分离
机制同策略分离是Unix设计中的基本原则之一,是将将程序的引擎(程序核心域的核心算法和逻辑规格)从接口部分(接受用户命令,展现结果等)分离;由于在一个系统中策略变化相对较多,例如预订服务的三级业务中之后并不须要再同步订单上的预订状态,若是策略的变化影响到机制会使得系统很不稳定,有需求修改时会致使系统大的修改,在功能上线须要QA验证的范围也会很大;致使策略变得死板,难以适应用户需求的改变,任何策略的改变都极有可能动摇机制。
机制同策略分离的机制引用最普遍的是MVC模式。
在这里预订流程的基本模型就是咱们的引擎,在引擎中规定了几个基本的业务节点,而每一个业务点的实现都有各自的接口规定,若是有需求的变动只须要更改各个业务节点自身的接口实现便可。至于如何接收支付结果发起预订,以及何种状况下反馈预订接口都是与核心流程分离的。
重构最痛苦的部分是怎么把项目上线。
在这几回的重构中,主要实行了两种重构:
项目内部逻辑重构,但没有新建数据表,对外的接口没有修改;
修改了对外的接口,新增了数据表。
第一种模式重构,在上线时比较容易,由于基本不用考虑到新老逻辑兼容的问题,第二种模式的重构在上线时须要考虑新老接口的兼容。在此次预订服务重构过程当中,修改了对外接口新增了数据记录,并且重构后的系统逻辑也与新的数据表耦合,所以在新老接口上须要作特别的兼容。此次预订服务改造主要涉及到发起预订和预订反馈,所以在兼容上须要在新老逻辑的入口上都须要作数据转换。另外在测试阶段须要模拟上线的步骤,校验上线每一个阶段的新老接口兼容如何,功能是否正常。
分拆上线
通常重构的部分不宜过大,过大时须要考虑的兼容就更多,影响到的外界系统也会更多;通常重构最好的方法是分步重构,重构一部分以后验证上线,小步快跑的方式上线。
在此我向你们推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
在这个重构的过程当中咱们主要有一下基本的原则:
机制同策略分离
协议统一化和简单化
开闭原则
主要使用到一下设计模式:
1.代理模式
2.监听者模式
3.责任链模式
4.装饰者模式