|
1、背景1.2业务重组与合并1.2解决技术债务1.3影响开发效率1.4监控体系不够完善2、重构目标3、设计3.1调研3.2规划3.3修缮者模式3.4领域设计(横向拆分)3.5模块设计(纵向拆分)3.6代码设计4、上线过程5、监控6、总结1、背景1.2业务重组与合并 随着需求不断迭代,转转消费分期整体出现了一些调整,并提出了新的产品方向,在此背景下,对于经历了久经沧桑的历史服务,已经逐渐不适合未来的产品规划。面对新的业务整合和重组,急需新的架构和思想来承载未来的业务。1.2解决技术债务现阶段存在的主要问题:代码模块之间边界感不强,需要通过模块拆分、服务拆分来区分业务边界。代码实现缺少层次感,设计模式单一,一层到底的冗长代码。 此前,微服务拆分原则是按消费分期、合作方分期产品等维度进行整体拆分的,优点是明确了项目职责,简单的从需求维度进行服务拆分,确实是一种行之有效的方式,缺点是没有对基础功能进行剥离,以至于很多场景需要维护重复的代码,增加了项目的维护成本。1.3影响开发效率 即使我们接手项目已经有一段时间,并对项目足够了解时,但排查问题起来依然费力费时,而且系统内部代码错综复杂,调用链路交错,难以正常维护,从长远的开发效率考虑,尽快提出新型方案来代替现有结构。1.4监控体系不够完善 线上异常机制不够敏感,缺少关键业务指标的告警看板,作为一个业务开发,应保持对核心指标数据的敏感性。2、重构目标不影响业务的正常运转和迭代;改善现有代码结构设计,让代码易于扩展,提升开发效率;采用新工程逐步替代原有接口,旧工程逐渐废弃。3、设计3.1调研开始重构之前,调研了互联网消金通用的架构解决方案:通用方案 由于是外部调研的通用架构设计,所以并非完全契合转转消费分期产品,但可以借鉴其分层架构设计的思想,在代码设计阶段,可以先对核心模块进行拆解和规划。3.2规划 前端页面与后端重构计划分两次迭代进行,分阶段进行,可以有效分摊并降低项目上线风险,第一次迭代围绕后端主要模块进行剥离重新设计并上线;第二次重构目的是解决产品需求,对接前端新页面。3.3修缮者模式 作为一个一线的业务开发,需要开展重构工作的同时还得保证产品需求的正常迭代,修缮者模式无疑是最佳选择。 第一次迭代历程,对于历史工程边缘逻辑保留并隔离,只对核心代码进行重构后转移到到新工程,新工程逐步接手老旧逻辑,并对老工程提供RPC接口,逐渐取代。此方案整体风险最低,同时能兼顾到正常的需求迭代。 第二次迭代历程,经历了第一次迭代后,新系统运行稳定,同时也具备接手新产品的能力,新工程开始与前端对接、联调,在此之后,V2版本也正式上线。3.4领域设计(横向拆分)模块拆分聚合业务:涵盖了消费分期主要业务,根据其各自产品需求特点,作为上层业务代码,对前端、收银台提供聚合接口。基础服务:用户信贷所产生的数据、或依托合作方数据,围绕金融分期服务提供的数据支持。三方对接:基于转转标准API下的逻辑实现,同时具备灵活接入合作方接口的能力。3.5模块设计(纵向拆分) 基于以往项目存在的问题,再结合消费分期的特点,我们对分期购买到账单还款结清的整个流程进行拆解:用户主动填写申请信息,提交授信申请并获额,挑选商品分期下单,生成还款计划,提供绑卡、账单还款等功能。以上就是一个简单的分期购物流程,基于以上流程,我们把消费分期所包含的公共模块,如授信前获额、用信、账单还款,这些富有金融服务属性的功能进行剥离。消费分期作为转转的产品原型,在聚合层中各自维护,互不影响。 设计原则:在不改变原有代码逻辑的情况下,根据单一职责和依赖倒置原则的思想:对系统进行模块拆分与合并,以明确项目职责降低耦合度;对包进行重新规划,划分包与包之间的边界,进一步减少代码间的耦合。3.6代码设计 好的代码重构一定离不开设计模式,基于原有单一的策略模式,我们把合作方对接模块与基础服务模块进行了拆解,采用双层模板、策略、工厂模式的组合,分别对授信、用信、贷后几个模块单独设计接口,维护好对合作方通用标准API接口,同时具备灵活接入的特点,举个例子,以下为授信模块主要代码类图:第一层作为基础服务的策略模式;第二层作为合作方对接的策略模式。主要类图设计: 在定义接口与实现类后,形成了对合作方对接层依赖,同时对订单、用信、授信等核心数据进行落地,对消费分期提供数据支撑,举个例子,以下为授信模块主要代码:基础服务接口定义/** * 授信接口定义 **/public interface ICreditService { /** * appId,资方定义的一个唯一ID */ String getAppId(); /** * app名称 * * @return zz or zlj */ String getAppName(); /** * 获取授信结果 * * @return result */ CreditResult creditResult(String logStr, Long uid);}标准流程抽象/** * 标准API对接实现 * **/public abstract class AbstractCreditService implements ICreditService { /** * 标准API对接 * * @return IBaseApiService */ protected abstract IBaseApiService getApiThirdService(); @Override public AppConfig getPartner() { return commonConfigUtil.getAppConfig(getAppId()); } @Override public CreditResult creditResult(String logStr, Long uid) { CreditResultInput input = new CreditResultInput(); input.setUid(uid); ResponseProtocol output = getApiThirdService().creditResult(logStr, input); String creditStatus = TransformUtil.approvalStatusTransform(output.getData()); return CreditResult.builder().result(creditStatus).build(); }}/** * 合作方差异化接入 */@Service@Slf4jpublic class ZZABCCreditServiceImpl extends AbstractABCCreditService { @Resource ZZABCThirdServiceImpl abcThirdService; @Override public String getAppId() { return artnerEnum.ABC_ZZ_API.getAppId(); } @Override public String getAppName() { return AppNameEnum.ZZ.getValue(); } @Override protected IABCThirdService getABCThirdService() { return abcThirdService; }}标准API对接/** * 标准API对接 * * @author Rouse * @date 2022/4/24 13:57 */public interface IBaseApiService { /** * 标准API,获取appId * * @return appId */ String getAppId(); /** * 获取授信结果 */ ResponseProtocol creditResult(CreditResultInput input);}内部标准API实现/** * 合作方,标准API对接实现 * * @author Rouse * @date 2022/4/24 14:04 */@Slf4jpublic abstract class AbstractBaseApiService implements IBaseApiService { @Override public ResponseProtocol creditResult(CreditResultInput input) { // 通用加解密 return getDataResponse(logStr, getAppConf().getUrl4CreditResult(), input, CreditResultOutput.class); }}差异化合作方接入/** * ABC合作方接口封装 **/public interface IABCThirdService extends IBaseApiService { /** * 标准API,获取appId * * @return appId */ String getAppId(); /** * 获取授信结果 */ ResponseProtocol
|
|