背景和需求:
上半年在我们的钱包app里做了一个类似余额宝的功能,下半年开始要推广了,和大部分公司的推广手段一样,邀请新用户开户成功之后给推广者发奖金;举个例子,余额宝的年化收益是 1.7% p.a,邀请一个新用户年化利息就加 1%,邀请2个加 2%......当然有最大限制。。用户今天余额是10块钱,今天推广了5个人,那么用户明天的余额就是(实际比这个复杂点):
本金:10
昨天利息:A = 10 * 1.7% / 365
昨天奖金:B = 10 * 5% / 365
昨天总收入:A + B
需求分析:
1、我这边需要根据 邀请人数计算用户每天能拿到的推广奖金;
2、(扩展)且需要支持以后的其他项目,比如说以后做借呗,需要为借呗计算每一期的信息数据;或者更换了一个新的推广活动,有了新的计算规则。。。总之一句话,就是可以计算 everything。
方案一:
由于我们这个计算奖金功能是在A服务里完成的;那么可以在A服务里新建接口,按照类型type去判断是什么场景,然后A服务的同学去编码写每种场景的实现。
但是这样的话,会带来很多问题:
1、A服务的同学只熟悉A服务,对于其他的服务B(借呗业务)不了解,不方便A服务的同学编码;
2、如果是接入方B(借呗业务同学)去A服务里编码自己的业务的话,由于每个服务的工程与项目写法不同,也会给B服务的同学带来编码效率问题
3、所有的业务实现都写在A服务里,每次改动计算规则,哪怕没有A服务的改动,那么A服务也需要重新code、test、deploy...
由此来看,方案一不可取
方案二:
因为是要计算everything,那么我们需要抽象出一个计算接口 calculateService,该接口不依赖任何业务,隔离所有场景,只做计算。
1、我们对外暴露calculateService接口,calculate方法,入参和出参都是map
2、接入方如果想用A服务的calculate接口,需要做以下准备:
(1)接入方需要自行实现 calculateParse 接口,完成自己具体的业务逻辑的编码
(2)需要把具体的业务逻辑的编码,放进A服务的calculate_rule表中;也就是要按照A服务的要求,提供dml文件给A服务的同学
3、A服务的同学需要做的是:
(1)在项目启动的时候,完成对calculate_rule表的读取,并构造 groovy 对应的xml格式
(2)把构造好的xml,交给spring容器去管理,这样A服务里就可以拿到calculateParse接口的具体实现
(3)当calculate_rule表中的计算代码有改动时,不需要重启A服务,只需要通过一个开关就可以refresh内存里的计算规则即可
这么做的话,会有以下优点:
(1)A服务只负责提供计算功能,完全隔离业务;
(2)具体的业务,由对应的业务同学进行编码,编码完成之后,提供dml给A同学导入DB中
(3)当某些计算规则需要 增加、修改、删除时,也是通过 sql 文件进行处理
(4)sql文件执行完成,不需要重启服务器,只需要推一下开关即可
(5)减少编码带来的bug,节约coding、testing的时间,提供迭代效率
选型
做这个需求的时候,调研了groovy、spring expression language、velocity 三种动态引擎,发现后两种比较适合做一些规则配置,而我这个需求设计到完整的业务逻辑,所以我选择了groovy。
spring expression language
https://www.baeldung.com/spring-expression-language
像我们公司的鉴权,就是用该语言做的;
举个简单的例子,拿用户付款的场景来说,前提是需要用户已存在并且状态是可用的;
一般公司里都是让用户服务暴露 queryUser接口,其他接入方调接口进行判断;
但是如果服务较多有几十个或者上百个,每个服务做鉴权都是重复的逻辑和代码,那这显然是不好的;
而且这还只是最简单的鉴权,还有很多鉴权场景需要判断接口的返回码 or 是否是中国人等多种鉴权的组合和嵌套;
这种情况如果写在代码里的话,后期不好维护和扩展,每次都要改代码,增加bug几率。
如果使用 spring EL 表达式的话,只需要在表里这么配置就OK了。
用户存在且状态可用:UserStatusFactor.exists == true && UserStatusFactor.userStatus == ENABLE,其中 UserStatusFactor 是我们程序里的类,他有 exists 和 userStatus两个字段,查询相关的表给这两个字段赋值,然后利用spring expression去匹配我们DB里配置的规则即可。
判断接口的返回码 or 是否是中国人:ThirdPartyFactor.reasonCode == 102 or ExternalPerson.nationality == chinese
然后会有很多已经存在的规则之间可以相互组合,相互搭配,扩展性很好,给某些场景增加或修改鉴权规则时,只需要修改sql,不需要改变代码。除非是那种之前一直没有过的鉴权规则,需要开发一套,然后再给以后的复用。
velocity
是一种基于Java的模板引擎,其使用场景和思路和spring EL类似,用法就直接看教程了http://velocity.apache.org/
groovy
https://www.bootwiki.com/groovy/index.html
是一个基于Java 虚拟机的敏捷动态语言。
无缝集成 所有已经存在的 Java 对象和类库。
直接编译成 Java 字节码,这样可以在任何使用 Java 的地方使用 Groovy 。
在开发 Web,GUI,数据库或控制台程序时 通过减少框架性代码 大大提高了开发者的效率。