• 三方系统多渠道多场景的思考及代码分享


    一 背景

    我们的业务中,经常会遇到多渠道+多场景的的需求。多渠道我们可以理解为,多个服务商同时为我们某一个功能提供服务,多场景就是同一个服务商下面的多种服务,我们通过几个例子来具体看下:

    我们有个电商平台,系统在付款时候弹出选项,让用户自主选择支付宝/微信/京东白条等支付,那么这里的支付宝、微信、京东我们都可以理解为他是我们的渠道。
    同时,既然接入了支付宝/微信等,我们的系统就不再会单单使用他们的一个支付功能,还会涉及到对账、退款等等操作,那么这些操作,我们称之为场景。

    类似的,有的公司需要将订单进行推送,根据订单类型,推送到淘宝、拼多多等。那么淘宝和拼多多也属于渠道,同样的,有了下单,那么还会有退单、订单对账等都属于场景。通过这样的案例分析下来,我们会发现,我们身边有很多这样的需求。如果针对这类需求,我们的代码缺少一些架构设计,那么就会带来很多问题,比如:

    • 代码复用度不够,新增需要会产生大量重复的冗余代码;
    • 代码耦合度过高,业务逻辑和三方交互代码夹杂在一起,每次改动牵一发而动全身,影响面不好评估。

    此类问题应该存在一些通用的解决方案,可以实现以下特点:

    • 面向接口编程:业务方调用三方服务的时候,不需要关心具体服务商的实现,只针对接口调用;
    • 对拓展开放,对修改关闭:每次新增一个渠道,或者新增一个场景,对已有的代码应该是无冲突的,不需要改动的。

    二 案例分析

    2.1 下单支付案例

    我们以下单支付为案例,通过时序图的流程来梳理下我们的思路:

    关于和三方请求的操作,一般就分为两类:

    • 请求三方系统;
    • 三方系统处理完成,回调通知。

    2.2 请求类

    请求三方系统的具体流程如下:

    不同场景下,流程相同,但是还会存在一些差异点,我们总结一下:

    • 通用:数据加签/加密,重试+告警,返回结果解密,操作日志记录等;
    • 差异:模型转换字段不一样,发起请求的方式和路径不一样;

    2.3 回调类


    不同回调场景也可能会存在一些差异,我们总结一下:

    • 通用:回调日志记录,数据解密,响应三方系统;
    • 差异:模型转换字段不一样,执行业务操作不一样;

    2.4 渠道商的拓展

    多渠道多业务通过以上分析基本上都是可以枚举的,可以通过以下表格进行拓展:

    三 代码实现

    代码已开源到Github:https://github.com/Shiyajian/mall-example
    代码可能运行有问题,主要展示的是思路,下面是针对项目的讲解。

    3.1 设计思路

    通过案例分析,我们分析出来了通用和差异点,对于通用的部分,我们采用封装成标准流程,差异的地方,我们定义拓展点接口,不同渠道方各自实现,每个渠道方的代码要物理隔离。

    3.2 代码结构分包

    代码主要分为三部分:核心业务代码、架构代码、拓展实现代码。

    • 核心业务代码:就是我们平常写的业务代码,应该和具体的三方系统交互解耦,面向接口编程,不感知三方系统模型,也不感知具体的渠道实现。
    • 架构代码:根据不同的渠道和场景,找到对应的实现,并且提供部分流程编排能力
    • 拓展点接口定义:抽象的能力接口和公用;
    • 拓展点实现代码:不同场景不同渠道的实现。

    3.3 spi 接口定义


    主要使用策略模式,这里通过接口进行抽象定义,代码主要分为三个部分:

    • call :表示调用第三方渠道商的抽象服务接口;
    • callback:表示第三方渠道商回调业务系统的接口,每个场景按包进行细分
    • common:一些公用的枚举和标记性接口定义

    拓展方式:

    • 如果需要新增调用渠道商的接口,那么在 ChannelCaller中定义方法,在 spi-impl 中实现;
    • 如果需要新增回调方法,首先在 ChannelCallbackSceneEnum 中增加类型,在 callback 中新增一个包,新建一个 request 模型和 parser 模型,request 表示渠道方传入的参数,parser 表示如何转换成我们业务系统模型;

    3.4 spi 实现方式


    每个渠道商为一个package,里面实现 call 和 callback 定义的接口即可,按需自己增加常量和工具类等;

    3.5 spi的发现及路由


    通过 Manger 管理类进行查找具体实现,并进行一定业务逻辑的编排,处理日志记录,错误处理等通用流程。

    3.6 业务代码使用

    需要调用渠道商接口的时候,通过以下方式进行调用:

    @Override
    public String pay(ChannelCodeEnum channelCode, Object args) {
        // 1、创建支付单;
        PayOrder payOrder = new PayOrder();
        // 2、支付单入库;
    
        // 3、根据不同支付渠道,调用三方的支付单创建
        ChannelCaller channelCaller = channelCallerManager.of(channelCode);
        channelCaller.submitPay(payOrder);
    
        // 4、更新三方订单号入库
    
        // 5、返回前端唤醒参数
        return payOrder.getPayParams();
    }
    

    渠道商接口回调时候,通过下面方式进行参数的转换,转换完成后执行自己系统逻辑:

    @RequestMapping("/pay/{channelCode}/{bizNo}")
    public ResponseEntity paySuccessCallback(HttpServletRequest request,
                        @PathVariable(value = "channelCode") String channelCode,
                        @PathVariable(value = "bizNo") String bizNo) {
    
        log.info("进入[" + channelCode + "]支付成功回调:bizNo:[" + bizNo + "] ");
    
        ChannelCodeEnum channelCodeEnum = ChannelCodeEnum.ofCode(channelCode);
        return channelCallbackHandlerManager.run(channelCodeEnum, () -> {
            CallbackPayRequest payRequest = channelRequestParserManager.parse(channelCodeEnum, ChannelCallbackSceneEnum.PAY_CALLBACK, request, bizNo);
            return payService.paySuccess(payRequest);
        });
    }
    

    结束语

    上面就是我在项目开发中,针对多渠道+多场景的一些思考及个人的代码设计。由于经验有限,此方案不一定是最优方案,欢迎大家批评指正,感谢。

  • 相关阅读:
    学习完C++ 并发编程后 手写线程池 最简单的线程池
    静态HTML旅行主题网页设计与实现——联途旅游网服务平台网(39页)HTML+CSS+JavaScript
    巧用CSS3之雨伞
    【代码精读】ATF的异常向量表
    自学Python系列(四)—— 字典和集合(一)
    软件测试怎么去介绍一个项目的测试流程?
    有没有一段代码,让你为人类的智慧所折服
    Git版本控制
    Linux -- httpd服务
    重绘与重排(回流)
  • 原文地址:https://www.cnblogs.com/shiyajian/p/17570040.html