支付路由-上

支付路由是我主导重构的,我觉得这应该是我得意作品之一吧。本次分享的话分为上下两块,上部分讲我是怎么设计的,下部分讲我是怎么重构的。

上部:我是用领域设计的方式来设计支付路由,领域设计是我之前在一家公司面试的时候,面试官问我知道领域设计吗?当时我有点懵逼。后来他引导我说java面向对象,一个类不仅应该有属性,还有方法。但是我们用MVC方式一个实体类只有属性,没有方法。后来回去后我看了相关的资料,领域设计是站在一个高点看待问题,抽取领域类和领域行为。(哈哈,这不就是面向对象的思想,抽象的看待一个问题)渐渐的喜欢上了这样的方式,是我以后解决问题的方法论

对于不熟悉支付的同学先介绍下什么是支付路由:用户在前端选择一种支付方式,比如使用招行借记卡来支付后,系统不一定就是调用招行的接口来执行支付。支付宝、京东、先锋等第三方支付平台以及银联等都支持招行借记卡支付。 这种将支付方式落地到具体的支付接口的模块,就是支付路由

需求背景

在公司快速发展的背景下,为了支付通道的稳定性,公司将陆续接入不同的支付渠道。在渠道出问题时,能快速的切换到其他支付渠道。不同的支付渠道的成本、稳定性、可靠性各不相同。需要选出综合最优的支付渠道在目前阶段,现阶段简易的支付路由,存在一些问题。路由代码阅读性差,难以维护和扩展。加之路由测试不好测试

设计目标

支付路由在支付系统中的核心作用,除了本职工作路由外,还承担如下职责:

  • 支付渠道的稳定性、可靠性。减少无法连接、不稳定、性能低的通道
  • 费率,通道省钱,哪个通道便宜,用哪个

领域设计

通用语言

通用语言是一种程序员和领域专家沟通的语言(和需求方沟通),只有相互理解需求,才能更好的开发需求

名词 解释
支付渠道 第三方支付公司,比如京东支付、翼支付、网信贷款、联动优势
支付通道 支付通道是支付渠道下开设的商户号,比如京东支付可以开通多个商户号,不同商户号直接没有关联
支付组 包含一个或多个支付通道
入金账户 功夫贷的贷款资金源,不同的资金源有一个主支付组和一个备用支付组
主支付组 资金方提供的支付通道
备用支付组 大树提供的支付通道
出金账户 用户还款时绑定支付的银行卡
入金账户 功夫贷的贷款资金源,不同的资金源有不同的支付渠道
路由 根据出金账户、金额、入金账户等因素进行筛选,最终获得一个最优的支付通道

领域思想

支付路由 当我们看到这个需求时其实有一些懵对不对?让我们一点一点的来补充这个语句

扩展一

支付路由分为两块,一个是业务路由,还有一个只支付通道路由

  1. 业务路由功能
    根据规则确定入金账户(大树的借款资金不是自己的,是不同的资金方。所以还款是还到响应借款时的资金方)
  2. 支付通道路由
    支付订单根据规则从支付组中确定最优的支付通道

上部分的话讲支付通道路由,所以下面讲的都是支付通道路由设计思想


扩展一

扩展二

支付订单根据规则确定最优的支付通道,那么规则有哪些呢?

熟悉支付的同学知道,有些支付通道是需要签协议的(协议支付)没有签协议是支付不成功的,支付通道还有限额,不同的银行限额还不同等。可以看到,要走这个通道不要满足这些必要条件的,我称这些条件为硬性规则

当然也有软性规则,支付通道错误次数(支付通道返回系统繁忙等)、用户在该支付通道错误次数(用户还款失败)等。这些失败并不代表支付通道不可使用,这些情况下我们可以把这些支付通道的权重下降。支付通道返回系统繁忙这样的事件在一小时内的统计达到一定数量,可以将该规则设置为硬性规则

到底有哪些规则呢?询问领域专家

硬性规则
  • 协议签约
  • 支付方式
  • 银行限额
  • 银行升级
  • 银行节假日(银行维护时间)
  • 支付通道升级
  • 支付通道节假日(支付通道维护时间)
软规则
  • 通道费率
  • 通道支付平均延迟
  • 通道错误次数
  • 用户错误次数
  • 功夫贷日限额
  • 功夫贷日限次
  • 功夫贷月限额
  • 功夫贷月限次

为什么功夫贷日限额或者功夫贷日限次等为软规则呢?我们希望用户能尽量等还款成功,这边等限额是为了让功夫贷用支付通道支付金额等限制,比方我们限制用京东支付日限额100W,尽量的平分给其他等支付通道合作方。

扩展二

扩展三

我给这些领域对象添加些属性,大家也可以帮忙看下是不是合理

扩展三

扩展四

支付订单根据规则确定最优的支付通道,现在支付订单、支付通道、规则都有了。怎么使用他们呢?我之前在同盾使用规则引擎,能动态的修改规则,所以这次我把规则引擎引入了进来,使用Drools。

Drools 是一个基于Charles Forgy’s的RETE算法的,易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。 业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。其他的自己去官网看吧

规则引擎的优点:数据位于“域对象”中,业务逻辑位于“规则”中。
现在规则有了,域对象是什么呢?---就是我们的支付订单和支付通道,其实就是我们运行规则的参数

扩展四

扩展五

现在我们的领域对象和行为都有了,现在给出它们的时序图

扩展五

从图中我们可以看到,执行规则路由只需要
1、构造支付订单
2、构造支付组下的支付通道
3、加载规则文件
4、运行规则

发现支付组下的支付通道是固定的数据,规则文件也是固定的。需要每次都去加载吗?所以这是一个优化点,可以加个缓存

扩展六

刚才说了,我们可以动态修改规则。可是现在规则是写在drl文件中怎么修改呢?
先看下drl的文件格式

rule "check 银行节假日"
    when
        $context: RouteContext($bankId          : payOrder.outPayAccount.bankId,
                              $holidays         : payPlatform.bankHolidays,
                              $current          : payOrder.payTime,
                              $channel          : payPlatform.code)

        $action  : RouteAction()

        eval(generalLogic.isHoliday($holidays,$current,$bankId))
    then
        HitRule hitRule = new HitRule();
        hitRule.setDesc("通道:"+$channel+",银行:"+$bankId+"正处于节假日");
        hitRule.setRuleName("check 银行升级");
        $action.addStrongRule(hitRule);
        $action.setInterrupt(true);
        drools.halt();
end

rule "check 通道节假日"
    when
        context: RouteContext($holidays     : payPlatform.payPlatformHolidays,
                              $current      : payOrder.payTime,
                              $channel      : payPlatform.code)

        $action  : RouteAction()

        eval(generalLogic.isHoliday($holidays,$current,"0"))
    then
        HitRule hitRule = new HitRule();
        hitRule.setDesc("通道:"+$channel+"正处于节假日");
        hitRule.setRuleName("check 通道节假日");
        $action.addStrongRule(hitRule);
        $action.setInterrupt(true);
        drools.halt();
end



rule "check 通道错误次数"
    when
        context: RouteContext($statistics           : payPlatform.payPlatformStatistics,
                              $stableWt             : payPlatform.payPlatformStableWt,
                              $channel              : payPlatform.code)
        $action  : RouteAction()

        PayPlatformStatistics($value:value) from statisticsService.query($channel,PayPlatformStatisticsType.ERROR_COUNT)

        PayPlatformStableWt($errorCountWtList:errorCountWt) from $stableWt

        eval( $value !=null && $errorCountWtList != null)
    then
        double score = generalLogic.getWeight($value,$errorCountWtList);

        HitRule hitRule = new HitRule();
        hitRule.setDesc("通道:"+$channel+",通道错误次数:"+ $value+",得分:"+score);
        hitRule.setRuleName("check 通道错误次数");
        hitRule.setScore(score);
        $action.addSoftRule(hitRule);
        if(score == 0){
            $action.setInterrupt(true);
            drools.halt();
        }
        $action.addScore(score);
end

rule "check 用户错误次数"
    when
        context: RouteContext($statistics           : payPlatform.payPlatformStatistics,
                              $stableWt             : payPlatform.payPlatformStableWt,
                              $accountNo               : payOrder.outPayAccount.accountNo,
                              $channel              : payPlatform.code)
        $action  : RouteAction()

        PayPlatformStatistics($value:value) from statisticsService.query($accountNo+"_"+$channel,PayPlatformStatisticsType.USER_FAILED_COUNT)

        PayPlatformStableWt($userErrorCountWtList:userErrorCountWt) from $stableWt

        eval( $value !=null && $userErrorCountWtList != null)
    then
        double score = generalLogic.getWeight($value,$userErrorCountWtList);

        HitRule hitRule = new HitRule();
        hitRule.setDesc("用户银行卡:"+$accountNo+",通道:"+$channel+",用户错误次数:"+ $value+",得分:"+score);
        hitRule.setRuleName("check 用户错误次数");
        hitRule.setScore(score);
        $action.addSoftRule(hitRule);
        if(score == 0){
            $action.setInterrupt(true);
            drools.halt();
        }
        $action.addScore(score);
end

观察规则 银行节假日与通道节假日、通道错误次数和用户错误次数。是不是感觉格式一样,只是参数类型不一样。

function holiday(bankId,holidays,currentDate,channel){
}

function count(statistics, stableWt,key,channel){
}

能否抽象出这样的模板呢?每个规则可以对应这样的模板,前端配置时选择模板,入参为支付订单或者支付通道中的属性。

扩展六

总结

这次主要是记录下我设计支付路由的思路,感觉没把领域设计思想说清楚,我总结下吧。
1、清晰的解读问题域(需求)
2、提取领域对象(在这个问题域中的实体对象)
3、建立归纳关系(即继承、泛化)
4、建立类间关联(聚合 组合 依赖)
5、开发关联类(类与类之间需要某个对象作为媒介)
6、时序图,描述了系统在运行时间的行为,包括系统如何完 成这些行为
7、代码开发

额,还不懂的话。可以看下面向对象分析与设计框架,这里说明了什么阶段,需要产出什么。更加详细一点。

感觉这次的分享比较枯燥,需要认真的看下了,感谢你坚持到了最后。接下来进入年底了,有几次期末考试,可能更新速度上会放慢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容