话说,许久以前,起码在它(金融产品中心)独立之前,它散落在好几个系统中,对车金融业务线内部相关系统在业务场景提供一些支撑,譬如:获取金融方案,费用规则检验,金融方案试算等。然而,每次的需求迭代,痛苦的不仅仅只有研发人员,还有我们的测试人员。更重要的是,为什么小小的需求变更却需要那么久的研发工时,老板接受不了,其实我也早已接受不了,因为那个时候我刚开始负责金融产品模块(还没有一个系统服务)。我认识到重构迫在眉睫,箭在弦上不得不发,更认识到重构从长远发展的重要意义,得益于老板的信赖与支持,我带着重构金融产品的决心和一个新生的团队,开始了一段新的征程。
【注】:整篇文章共计5000多字,背后花费好几次北京(顺义-大望路)地铁全程旅程中手机码字,实属不易。此外,又多次润色。文笔不佳,敬请原谅。感谢读者能够真正认真读完,相信你有不少感悟和体会。
车金融|GPS审核系统的前世今生
车金融|基础数据平台的前世今生
车金融|合同中心系统的前世今生
车金融|金融产品规则引擎的前世今生(上篇)
车金融|金融产品规则引擎的前世今生(中篇)
车金融|金融产品规则引擎的前世今生(下篇)
车金融|我在M公司的那两年
依我之见,金融产品中心在车金融的地位与价值,在于提供不同资金方和区域运营城市,面向不同有车贷意向客户群体,提供差异化和个性化的贷款金融解决方案。
从系统架构设计来讲,金融产品平台提供产品运营人员辅助公司决策在不同资方和金融方案的差异运营决策的落地与执行;面向车金融业务线各系统和服务提供资金方准入,金融产品试算等业务场景支持。
重构的意义是在于明确金融产品中心的角色,对产品运营人员提高运营效率,对业务线提供稳定可扩展性的架构服务,快速满足公司业务需求迭代,缩短整体研发周期,提升项目研发效率。
重构不是一开始就可以落地,而是在不断地需求迭代和系统升级中不断总结经验,实践真理。重构的真正价值就是要量变引起质变,使得我们的系统服务更适应业务趋势发展的需要,使得我们的系统服务更符合整体的架构思想,使得我们的系统服务更具有灵活的扩展性和伸缩性。
重构之前,金融产品并没有一个独立的应用系统,提供产品运营的功能维护在公司内部是一个相对古老而代码臃肿的基于SpringMVC+Hibernate+JSP架构的系统。对于收单系统和审核系统来讲,则是各自处理金融方案业务逻辑,共用同一个数据库,你查询你的数据,我修改我的数据,各自为营,各自为战的。
此外,我接触金融产品时,为了支持相关业务场景,还有十几个MySQL存储过程(是否你不可思议,什么年代了,还有这玩意。顺便提一下,那时2018新年伊始,我是2017年9月初入职)。它造成了每次需求迭代(譬如对接资金方、金融方案调整、前后规则调整等),面临着多个系统都需要进行代码逻辑变更。然而事实上,这两个系统是属于两个不同的小组团队各自维护,只要每次涉及金融方案相关需求调整,两个系统都需要进行研发,正所谓“一个不能少,想跑跑不了。你跑我也跑,需求谁来做。”。
看似只是业务逻辑调整,但各个应用系统都需要或多或少调整代码逻辑,由于金融产品涉及不同的业务逻辑case,这就造成了其代码的复杂性,可维护性非常差,并非一个Java工程师就能简单调整齐全逻辑没有漏洞,不绝如缕,不仅在给测试阶段带来测试的复杂性,同时也带来线上的不稳定性。
老板曾经讲,按规划那一年需要接入更多的资金方,需求将会不断接踵而至,这样就不得一次次占用重复的研发时间。同时老板觉得为什么金融产品需要如此长周期研发才能上线,简直不可思议,我们知道老板是无法一次次原谅这种情况继续存在的。
另外,十几个MySQL存储过程,看似总量不多,但是好几个都是好几千的代码量,大量的case when 以及ifnull在你阅读代码时不断映入眼帘,应接不暇。长篇章的代码量不仅阅读起来困难,而且想要进行逻辑调整以及问题排查更是困难重重,难上加难。虽然个人工作履历做过几年Oracle PL/SQL编程,但从设计上和代码跟踪上是大径不同,相距甚远。坦白来讲,互联网使用MYSQL开发存储过程,灾难将会在某一刻瞬间让你怀疑人生,如坠深渊。
曾经有一次涉及一个资方前置规则需要调整,测试环境正常测试通过。上线时,我们把存储过程SQL脚本,通过DBA在线上执行,编译正常通过。然而,线上回归测试时,测试其中前置规则检验场景时,Java服务端通过Hibernate调用存储过程时,却捕捉异常,具体的异常信息你却摸不着头脑,不知道存储过程代码中第几行执行错误,想哭都哭不出来(跟谁哭,偷偷哭)。
没有办法快速解决该问题,我们上线都是在晚上,测试阻塞在这个环节。千钧一发时刻,我们同产品沟通了一个兼容方案。我们恢复备份的原存储过程SQL脚本,然后在审核系统审核环节后置规则增加该逻辑检验,虽然流程滞后,但依然能够保障规则检验生效。之所以后置规则能够方便调整,是因为审核系统基于Java语言硬编码(大量的if条件判断)实现的,对于我们服务端研发肯定容易上手,问题也容易排查。
基于上述背景和窘境,我们不得狠下决心,提出了重构金融产品中心的计划和目标,开启我们团队重构金融产品的计划,谱写我们的精彩故事。
重构之路虽然艰辛,但这段历程刻骨铭心,成就的不仅仅是我个人,还有那些共同参与项目的成员。“一个人的成功不算真的成功,带领大家共同成功,才算真正的成功”。
通过整理研究分析,我们制定总体的项目目标:重新打造金融产品中心,解决当前项目的痛点,对车金融业务线其他系统提供统一稳定的服务(你们几个系统以后都不用开发了,我们提供接口服务,就可以满足当前业务场景支持了,是不是一劳永逸,可喜可贺哈)。同时我们团队需要满足现阶段的正常需求迭代,保障需求如期正常上线,这样就需要我们以更短的时间投入到项目重构之中。
为了最短时间能够让老板看到重构的成果,同时缩短重构周期以及对线上的最小影响,作为项目重构总负责人的我,通过深思熟虑把这个大目标拆解三个分阶段去实现:
一阶段完成金融产品平台重构(提供给产品运营人员使用的),迁移原系统的功能菜单到一个新的域名平台,实现跟老系统的业务模块拆分,独立部署系统功能,为后续平台功能优化和不断升级奠定基础。
二阶段打造一个金融产品中心服务(提供给车金融业务线相关系统提供金融方案试算等接口服务),对外提供金融产品方案试算,金融方案获取,费用规则检验等主要功能,对接相关系统的业务场景支撑,实现一个金融产品团队即可完成后续需求迭代和服务升级,缩短研发周期,提高系统可扩展性和灵活性。
三阶段完成金融产品中心MySQL数据库的拆分,实现从原系统拆分出金融产品中心的自己数据库实例,真正实现服务提供和数据存储的独立部署维护,取消对原老系统的耦合依赖。
这三个阶段,是步步推进,步步为营,最终达成总目标的实现。此外存储过程的改造,也在并行中不断推进,一个个画上句号(这里感谢后来团队新来的同学小Zhao,以及前期支持的小Jia,支撑后来所有的存储过程测试研发以及最终上线)。
我们并非期望一口吃成大胖子,而是一步一个脚印,最终收获属于我们的胜利果实。
“大目标化小目标,大任务分解成小任务”,这是我们总体战略的基本思想,我们的基本宗旨就是"每一个小目标都要按期达成,每一个小任务都要漂亮完成"。
何谓"按期达成",就是我们拆分出的一个个里程碑目标,我们结合项目情况和资源情况,安排项目计划,然后汇报老板过目,让老板短期能看到一个个新的变化,肯定我们重构带来的价值,这样老板就能在惊喜中对下一个目标有所期待。
何谓"漂亮完成",就是我们要精益求精,一丝不苟,哪怕一个小任务,我们都要保持一颗细心和敬畏的心态,从开始到完成,做到一个字"漂亮",打胜每一次小仗。
大目标有了,分解的每一个小目标也有了,拆分出的每个人每天的任务都有了。我们携手并肩,坚持基本思想,秉持项目宗旨,每隔几日我们要跟进进度的完成情况,不断完善我的构思和想法。
在金融产品平台研发前期,我们制定了前后端分离的架构思想(前端基于VueJs,服务端基于SpringBoot)。这样以便后期需求迭代,前端和服务端的工程师可以各司其职,并行开发(不像原先前端使用JSP语言开发,作为服务端的我们还要编写HTML和CSS),方便系统维护和升级部署。
万事俱备,只欠东风。我向老板汇报缺少一个前端研发,老板给力申请一个前端工程师(小Hui同学,以及后来短暂支持的Mr Liang),服务端从架构组调来了一位猛将(小Kang同学),带上我共计两个服务端(最初)。伴随着重构的步伐和项目不断推进,后来老板雪中送炭,又从其他团队调来两个小伙伴(小Tao和小Xian’er)。
一阶段重构历时一个月研发与测试(我们自测新平台功能菜单,新平台与老系统并行使用,同时邀请产品运营同学帮忙测试)并上线,完成新平台拥有老系统的原有功能菜单,同时提高了相关功能交互体验。
新平台邀请相关产品运营用户,在一段时期试用,反馈的问题及缺陷及时修复上线,保障新平台功能正常,最终完成用户的平台全量切换,结束老系统产品模块的历史使命。
到了二阶段,参与这个项目重构的服务端只有四个人(包括我,其他是小Kang,小Tao,小Xian’er),所以我把四个人划分两个小组,我带领小Kang任务比较艰巨,需要完成金融产品中心核心试算逻辑代码编写以及对接提单系统的接口流量灰度接入,另外两个小伙伴(小Tao和小Xian’er,重构关键时期小xian’er被领导委以其他重任)完成审核系统对接和灰度接入。
技术方案设计时,我和小Kang同学一块讨论了灰度设计与系统接入方案。灰度方案需要支持,可以按照资方、产品,单量、是否覆盖原有试算数据等几个维度,提供更细粒度灵活的灰度流量接入方案,以保障我们重构上线对线上的影响降低最少,同时提高流量切入样本覆盖的多样性。
经过深思熟虑和研发工期评估,我们以最小的研发成本实现这块灰度方案的设计与代码实现,虽然不是披星戴月,但加班不再话下,以满足后续提单系统和审核系统的快速接入,确保团队兑现老板的上线质量承诺。
在设计金融产品核心试算代码时,多少个夜晚睡觉前以及上下班的地铁旅程中,一直思考这块的设计。鉴于考虑后期系统的扩展性和灵活性,引入了Google Aviator计算引擎(这里感谢小Kang同学的调研,事实上系统内部有基于JepExpression引擎的应用,但扩展性不佳),它有效的解决了我们可以自定义函数的灵活性和扩展性。此外,通过进一步抽象设计引入了设计模式(模板+策略+责任链)。
之所以如此设计,是因为在重构之初,通过思维图方式梳理了现有的业务逻辑处理,发现整个总贷款费用项每次需要依次经过各费用规则校验以及过滤筛选,并计算出每个独立费用项费用金额,最后累计到总贷款金额。通过前期大量梳理和研究分析,最终设计出此技术方案。
审核系统接入的复杂性在于,由于重构不仅涉及Java服务端,还有老系统若干个JSP页面(包含上千行JavaScript脚本)。我们可以想象最初设计通过JavaScript实现前端表单校验,但是难以想象这些JavaScript脚本中却包含大量的费用计算以及进位方式取整方式的处理,伴随需求不断迭代,很多场景线上已无需支持,但是这些遗留的代码遍及重构代码之中。
审核系统的对接开发,其实压力颇大。毕竟它涉及一个重要的环节-审核环节(初审/复审/终审),由于波及范围大,订单流程环节比较关键。那我们又如何重构它呢?
我们团队通过研究方案(感谢提出的方案的小Xian’er同学),提出了两种重构技术设计方案:
上述两种方案,可以看出代码开发量都不少;但影响最小的反而是第二种方案,但弊端在于伴随着需求迭代,需要维护新JSP和老JSP业务逻辑处理,倘若灰度时间越长,这种痛苦持续的也越长,开发和测试都需要测试这两种不同场景,成本可见很大。
我们把这两种重构方案汇报了领导,幸运的是老板也青睐第二种方案,但是提出一个要求,需要最短的时间灰度并全量切换。因此,我们在整个灰度阶段,安排重要人选对线上数据观察和样本数据分析,确保流量覆盖率,保障全量正常切换。
整个重构项目阶段,研发时间占据了大部分工期,老板期望的最晚上线时间近在咫尺,因此我们跟测试团队讲解了现阶段测试目标,并重点列出重点测试场景。测试期间,由研发配合测试确认各个case场景接口输入输出的业务逻辑正确性。
同时为了提高协作效率,申请了独立会议室封闭测试。事实上讲,这种方式效率真的大幅提升,毕竟我们想要反馈一个bug或者回复一个问题,无需再一个个字的敲,有时双方还不及时看,对相关回复理解偏差。
由于我们制定了严谨的上线方案,所以上线是非常顺利的。金融产品试算有一个灰度总开关,可以支持是否使用新的试算覆盖原有试算结果,这是我的降级方案的最简单设计。
同时在接入系统中,通过日志打印输出原有金融方案试算输入输出结果,这样便于我们人工观察流量试算情况,确保准确性,方便问题跟踪排查。
这个阶段坦白来说,是个比较枯燥的活儿。灰度期间,我们需要把当天流量的订单一笔笔核对各个费用项金额的正确性。倘若发现相关字段差异,则会安排人员进行问题排查,然后修复测试完毕并当日快速上线。
在此阶段,小Kang同学艰辛无与伦比。此活虽然难度不大,但要求一个细心的人来完成工作,最终小Kang同学完成了最终使命。
尽管我们通过"观察阶段"的过程,我们依然无法100%保障每一笔订单计算无误。所以,我们比较保守的每天流量放出来几个单子,并进一步观察,这样一天天逐渐放进更多单子,从1单到5单,从20单到50单,以最终完成全量切换上线宣告灰度阶段的胜利。
重构无止境、追求完美无懈可击是我们的团队使命。
经过一二阶段的重构,虽然我们拆出来独立的金融产品平台和独立的金融产品服务。但是这仅仅是一个开始,由于在这一年-2018年,也是一个不平凡的一年。那一年,由于负责金融产品的产品经理休假了,尽管如此,我们主动承接前线产品运营的需求,保障需求正常迭代。
为了了解产品日常运营的痛点,我们出差到前线城市,面对面沟通他们的当前问题。通过梳理这些痛点,在出差期间,我作为一个研发,从零学习Axure原型设计,并设计出初稿交互设计方案,经过和运营同学几轮会议沟通,最终确定一套功能设计方案。
深刻认识到前线用户的日常运营痛点,让我们铭记于心。带着责任和使命,我们回京和前端一块短期分优先级实现了这部分功能设计,并一个个迭代上线,赢得了他们的认可。
日常产品运营更多频次操作的是为了支撑不同资方、不同区域城市的多样化运营落地和执行,然而当前系统是无法支撑他们这种大批量操作和修改的,以至于他们经常需要通过SQL批量更新操作。虽然这种方式快捷,但是其风险也是很大的,鉴于老板的忧虑,期间也偶尔出现这种SQL操作方式带来线上故障。我们跟产品运营同学通过沟通,同时设计了一系列批量操作的功能交互方案。这些功能可以满足他们的日常运营需要,前后经过几次大的优化,这些问题最终一个个画上句号。
尽管系统拆出独立的服务,但是数据库依然公用原先的数据库实例。它带来的问题也是非常普遍的,由于很多系统虽然在重构拆除的独立服务,但是根本上,数据库大家都共用同一个。数据库实例的空闲连接和CPU是有限的,因此一旦出现问题,受影响的是这些依赖的服务和系统。
曾经线上就出现这种问题,由于大量慢查询SQL导致数据库连接无法释放,导致空闲连接占满,进而达到最大连接上限,影响了是整个链条的服务业务调用,这种灾难是非常可怕的,也是非常致命的。
因此2018年下半年我们就着手三阶段目标拆库工作,拆库的宗旨就是数据库表结构不变,以减少拆库带来的影响。拆库的实施方案时,上线时,我们选择一个合适的时间点-低峰时期,app暂停服务。
然后由DBA配合把老库相关表迁移到新库,完成数据迁移后,同时保留原有数据库相关表及数据,这时,服务数据源切换到新数据源,完成跑单全流程测试。翌日观察线上的情况,同时做好预案处理,倘若出现故障,服务快速切换到原发布版本Tag,最短时间恢复原来部署情况。预案不可少,毕竟应对突发情况。可喜可贺,翌日没有突发故障,我们再次完成了一个新的里程碑。
伴随着重构的尾声,它带来的最大的变化就是最初需要两个团队完成需求对接,现在仅需我们金融产品团队就可以完成。同时,我们自研的计算链路分析,使得无论在研发、联调、测试、还是线上,惠及众多的角色用户。使得我们可以方便跟踪每一笔订单金融方案试算请求和输出日志,以进一步排查确认问题,这无形中提高了各方面的效率,缩短了周期。
对于后续的需求,金融方案无论增加或调整何种费用项,从金融产品方案角度来讲,开发周期仅需简单的配置和最小的研发工期即可实现。这种对比重构之前花费3~4天的工时,可谓是华丽的质变。
艰辛的付出总会有丰厚的回报,这些重构经验同时也惠及其他团队,提供了经验积累和实践理念。那一年的岗位晋级,也构成晋级汇报上一道靓丽风景线,我可以分享这段历程和成果,赢得领导的肯定。
正如老板一句话"一个人的成功不算成功,带领团队共同成功才算真正的成功",这段历程不仅历练了我,同时也有我们的团队,也是从最初的重构开始,我们这个金融产品团队开始从此诞生并成长壮大,一次次不断谱写我们团队的辉煌历史。
在此,感谢这段重构历程中参与的每个人,是你们共同创造了这段辉煌历史,感谢你们!同时也感谢看到文章最后的读者,我讲的故事结束了,希望这个故事给你带来启迪和灵感。谢谢你们!!!世界因为你们而美丽!!!