支付路由是我主导重构的,我觉得这应该是我得意作品之一吧。本次分享的话分为上下两块,上部分讲我是怎么设计的,下部分讲我是怎么重构的。
上部:我是用
领域设计
的方式来设计支付路由,领域设计是我之前在一家公司面试的时候,面试官问我知道领域设计吗?当时我有点懵逼。后来他引导我说java面向对象,一个类不仅应该有属性,还有方法。但是我们用MVC方式一个实体类只有属性,没有方法。后来回去后我看了相关的资料,领域设计是站在一个高点看待问题,抽取领域类和领域行为。(哈哈,这不就是面向对象的思想,抽象的看待一个问题)渐渐的喜欢上了这样的方式,是我以后解决问题的方法论
对于不熟悉支付的同学先介绍下什么是支付路由:用户在前端选择一种支付方式,比如使用招行借记卡来支付后,系统不一定就是调用招行的接口来执行支付。支付宝、京东、先锋等第三方支付平台以及银联等都支持招行借记卡支付。 这种将支付方式落地到具体的支付接口的模块,就是支付路由
需求背景
在公司快速发展的背景下,为了支付通道的稳定性,公司将陆续接入不同的支付渠道。在渠道出问题时,能快速的切换到其他支付渠道。不同的支付渠道的成本、稳定性、可靠性各不相同。需要选出综合最优的支付渠道在目前阶段,现阶段简易的支付路由,存在一些问题。路由代码阅读性差,难以维护和扩展。加之路由测试不好测试
设计目标
支付路由在支付系统中的核心作用,除了本职工作路由外,还承担如下职责:
- 支付渠道的稳定性、可靠性。减少无法连接、不稳定、性能低的通道
- 费率,通道省钱,哪个通道便宜,用哪个
领域设计
通用语言
通用语言是一种程序员和领域专家沟通的语言(和需求方沟通),只有相互理解需求,才能更好的开发需求
名词 | 解释 |
---|---|
支付渠道 | 第三方支付公司,比如京东支付、翼支付、网信贷款、联动优势 |
支付通道 | 支付通道是支付渠道下开设的商户号,比如京东支付可以开通多个商户号,不同商户号直接没有关联 |
支付组 | 包含一个或多个支付通道 |
入金账户 | 功夫贷的贷款资金源,不同的资金源有一个主支付组和一个备用支付组 |
主支付组 | 资金方提供的支付通道 |
备用支付组 | 大树提供的支付通道 |
出金账户 | 用户还款时绑定支付的银行卡 |
入金账户 | 功夫贷的贷款资金源,不同的资金源有不同的支付渠道 |
路由 | 根据出金账户、金额、入金账户等因素进行筛选,最终获得一个最优的支付通道 |
领域思想
支付路由 当我们看到这个需求时其实有一些懵对不对?让我们一点一点的来补充这个语句
扩展一
支付路由分为两块,一个是业务路由,还有一个只支付通道路由
- 业务路由功能
根据规则确定入金账户(大树的借款资金不是自己的,是不同的资金方。所以还款是还到响应借款时的资金方) - 支付通道路由
支付订单根据规则从支付组中确定最优的支付通道
上部分的话讲支付通道路由,所以下面讲的都是支付通道路由设计思想
扩展二
支付订单根据规则确定最优的支付通道,那么规则有哪些呢?
熟悉支付的同学知道,有些支付通道是需要签协议的(协议支付)没有签协议是支付不成功的,支付通道还有限额,不同的银行限额还不同等。可以看到,要走这个通道不要满足这些必要条件的,我称这些条件为硬性规则
当然也有软性规则,支付通道错误次数(支付通道返回系统繁忙等)、用户在该支付通道错误次数(用户还款失败)等。这些失败并不代表支付通道不可使用,这些情况下我们可以把这些支付通道的权重下降。支付通道返回系统繁忙这样的事件在一小时内的统计达到一定数量,可以将该规则设置为硬性规则
到底有哪些规则呢?询问领域专家
硬性规则
- 协议签约
- 支付方式
- 银行限额
- 银行升级
- 银行节假日(银行维护时间)
- 支付通道升级
- 支付通道节假日(支付通道维护时间)
软规则
- 通道费率
- 通道支付平均延迟
- 通道错误次数
- 用户错误次数
- 功夫贷日限额
- 功夫贷日限次
- 功夫贷月限额
- 功夫贷月限次
为什么功夫贷日限额或者功夫贷日限次等为软规则呢?我们希望用户能尽量等还款成功,这边等限额是为了让功夫贷用支付通道支付金额等限制,比方我们限制用京东支付日限额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、代码开发
额,还不懂的话。可以看下面向对象分析与设计框架,这里说明了什么阶段,需要产出什么。更加详细一点。
感觉这次的分享比较枯燥,需要认真的看下了,感谢你坚持到了最后。接下来进入年底了,有几次期末考试,可能更新速度上会放慢。