大家好,我是奕叔😈,一个工作八年快退休的程序员,微信:lyfinal,交个朋友,一起交流Java后端技术,相互成长,成为更优秀的🐒~
使用场景
当面对复杂的业务进行开发时,程序本身逻辑代码和业务代码互相嵌套、错综复杂,代码一旦写死很难更改,而业务规则经常改变。这时候就需要使用规则编排来管理,这种业务模型的特性有着固定的输入参数和输出格式,中间规则部分要求能够配置化,尽可能灵活,不需要重新写代码。
学会了这个组件包括思想,以后能在工作中大量使用,你会看到很多业务部门当业务复杂时候做执行编排,规则管理除了使用很重的开源框架之外,基本都会自实现一个轻量的组件来完成,因为这样零依赖无侵入。
为什么需要规则抽象
我相信大家应该首先会有一个问题,什么是规则? 用最通俗的话来说规则就是在开发中一个业务PD提出来的玩法或者逻辑。比如在预售商品在商品详情页售卖,预售的开始时间是多少,结束时间是多少,这就是一个规则。规则决定着表现,然后开发按照此规则去设计、去编码。
然而,规则肯定不止一个,是多个。就拿预售商品在商品详情页售卖例子来说,除了有预售时间段规则,还有预售不是针对所有商品类型生效的,往往是部分类目下的商品才可以预售,那么这也将是一个规则,
这里引申下,如果是你们来设计,你是会把这个预售作为一种类型字段挂在商品主表上区分呢,还是会对商品进行打标处理呢?答案是打标,用小白的话解释就是打标就是对一类商品主体贴上标签,如果存储在db的话扩展非常之差,往往采用hbase宽表进行存储商品标。
那么问题来了,这么多规则,比如ABCD规则,是依次执行吗,如果存在某种商品类型只需要执行BCD呢,那么执行A不就无效调用了吗?另外是顺序又是怎能的呢,不同的场景执行的顺序可能不一样又是如何设计呢?规则执行存在依赖关系如果管理呢?等等 会碰到各种各样的问题。
倘若我们硬着头皮编码开发,前期的话你会感受到美滋滋,三下五除二就开发编码上线完成,等到迭代到一定的程度,产品规则越来越多,业务逻辑越来越复杂,这时候你就难受了,你改的代码你无法很有信心保证不会影响老的逻辑。这时候就可能为了安全上线,搞个灰度,if...else...上线,如果命中灰度则走你的逻辑,if else在手,天下我有!bug 退!退!退!

但是作为一个有点情操的程序猿,往往不会停止于这样。需要从业务上能看出这个整个链路上是怎样的,服务内上下层是如何调用组装的。
ok,那我们一步步来思考怎么来演练这种代码模型架构,如上面例子所说,规则这么多,我们肯定在开始设计的时候无法穷举所有的情况,需要抛开具体的业务规则来抽象出来顶层设计。因为不管是前端还是服务端对外提供的接口无外乎都是一系列方法或函数的串接组合结果。基本可以是以下的过程。
-
编写每一个方法函数
-
对所有方法和函数进行串接
-
构建返回结果
真正需要开发的是第一步,而这些方法函数都是可以高度抽象 之后复用的,每一个方法或函数只关注做具体的事,它不关注为什么业务而做,为哪个接口而做 。每一个方法函数无非两种情况,一种是通过此方法得到某结果,可以使从某个源或者三方接口得到的数据,也可以是执行特定业务逻辑计算之后的结果;另一种就是对某个数据发生改变,可以是数据源持久化,也可以是三方接口做的某些通知。将每个小业务规则抽取成独立组件,每个功能组件确定是否正常执行,将执行结果放到处理上下文中,规则组件之间通过抽象编排配置流程化。
组件设计
首先怎么识别是一个规则,可以采用Java特性注解。比方说识别一个方法有没有重写,看有么有@override注解是同样的道理,注解会让代码更清洁清晰。
注解的定义
首先先定义一个Rule注解,这个注解需要怎样的属性呢,毋庸置疑id肯定需要,识别规则唯一 ,name规则名称,如果一个规则比较复杂,实在很难用name来进行描述这时候就可以设计一个detail字段来备注规则详情。
less
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Rule {
/**
* 规则id
*
* @return
*/
String id();
/**
* 规则name
*
* @return
*/
String name();
/**
* 规则描述
*
* @return
*/
String detail();
}
处理器接口定义
规则注解定义好了,那么规则如何处理呢,有些规则可能是内存计算,有些可能需要请求三方接口等,形式不统一,那么这里就要用到接口 !提供统一处理方法进行抽象。这时候你会考虑接口的入参怎么定义?因为不知道具体的规则执行参数有哪些,都有什么类型。那么这里就需要用泛型。泛型能解决的是无需感知具体的数据类型。OK,接口雏形可能要出来了,一个接口提供一个方法,方法的参数是泛型,那不就是泛型接口吗,没错。
csharp
public interface IHandler<T> {
/**
* 处理事件Event
*
* @param t
*/
void onEvent(T t);
}
规则抽象实体
一般来说,有泛型的地方肯定就有继承,为什么呢?我们想下既然这里提供了泛型T,可能有这个T是A,也可能是B,是C。那么根据Java的特性,这里ABC有没有一些公共属性可以复用,答案是随着规则越来越多肯定是有的,所以在一开始这里就需要这样设计防止后期还需要重构。
kotlin
@Data
public class RuleBaseEntity implements Serializable {
/**
* 实体id
*/
private Long entityId;
}
规则实体上下文
为了处理实体在规则执行过程中内存数据的流转 ,何为内存数据,可以理解在某一步的值,需要在下一步需要使用到。最简单的就是传递过去,这是最low的方法也是最普通的方法,但是作为通用组件需考虑代码抽象,所以需要上下文来做这一层。先将上一步的数据放到上下文中,在下一步使用的时候从上下文取。整个过程都是基于内存操作,没有进行存储。所以称之内存数据。
那么这也会有另外一个问题,当内存数据在多线程的情况下发生窜乱的时候,会导致内存数据不安全。可能A线程先存了进去,但是取出去在B线程取了出来,这种就需要使用线程副本ThreadLocal来包装上下文,这也是最常见的套路搭配组合。
csharp
public class EntityContextHolder {
private static ThreadLocal<RuleBaseEntity> contextThreadLocal = new ThreadLocal<>();
/**
* 存入线程副本
*
* @param entity
*/
public static void setContext(RuleBaseEntity entity) {
contextThreadLocal.set(entity);
}
/**
* 从线程副本取出
*
* @return
*/
public static RuleBaseEntity getContext() {
return contextThreadLocal.get();
}
}
规则处理器和规则关系
既然接口泛型设计好了,那么这里会碰到下一个问题就是规则处理器的实现问题,我们定义的 IHandler 接口,在一个规则下可能有多个处理器,理论上我们要求是一个原子的处理器,怎么理解,比方说前文说的预售商品详情页售卖规则,预售的开始时间和结束时间需要依赖一个处理器来完成。预售针对什么商品类型生效需要依赖另一个处理器来完成,倘若你说这里放一个处理器来完成行不行,这里会有隐患。假设这里放在一个处理器中,无法保证多个无相关的操作是否会相互影响,比方说前者异常了就直接影响后者了,所以这里隔离出两个处理器合适。
处理好了规则和处理器之间的关系,能看出这里的关系是1:N的,也就是一个规则可以映射出多个规则处理器。
规则组的演变
写到这里,思考这里还会有什么新问题产生呢?不妨思考这个例子,我们假设:
css
A规则映射着编号为1,编号为2,编号为3的规则处理器;
B规则映射着编号为2,编号为4的规则处理器;
C规则映射着编号为3,编号为5的规则处理器;
现在有某一个M业务产品链路,这条链路上需要走A规则和B规则贯穿,另一个N业务产品链路,这条链路上需要走B规则核C规则贯穿。
你可能会这样设计,我将A规则和B规则进行合并,衍生出一个合并(AB)规则,里面包含编号为1,编号为2,编号为3,编号为4的规则处理器。也就是规则池中有4个规则(A、B、C、(AB))。
那么依法炮制,针对N业务产品链路相同的方法操作,规则池中就有了5个规则。相信也看到了,这回导致什么?
规则池的泛滥 。这是个致命的缺点,因为你后面无法一眼看出哪些规则可以复用。
为了解决此问题,需要用到数据结构中的树结构 。这是一个经典的思想,利用树层级关系特性来梳理关系 。所以针对上面的情况,我们可以不用增加任何新的衍生规则。在这些规则中划出很多条上层线条牵扯合并成上一层节点,注意这里理论上可以无限的往上,需要根据业务复杂度合理抽象。我们叫之为规则组。
规则Node抽象定义
前者我们定义了规则注解作用打上类头上,为了更好地定义规则节点,这里我们使用抽象泛型类定义(原理和上面说的规则处理器是一样的),一个规则具备多个规则处理器,我们不能开放这种处理的能力对外,根据Java的封装特征,规则和处理器之间就相差一个发出指令,可以理解由规则向规则处理器发起。所以我们需要在抽象规则中定义执行规则处理器的方法。performActions。既然规则和规则处理器是一对多的关系,这里根据封装需要在抽象规则定义中可以对规则处理器进行添加addHandler ,这也是委托的设计模式。
less
public abstract class AbstractRule<T> {
/**
* 规则Id
*/
@Getter
@Setter
private String id;
/**
* 规则名称
*/
@Getter
@Setter
private String name;
/**
* 规则处理器
*/
@Getter
@Setter
private List<IHandler<RuleBaseEntity>> handlers;
/**
* 满足的触发条件
*
* @param t
* @return
*/
public abstract boolean evaluateConditions(T t);
public AbstractRule<T> assembly(AbstractRule<T> aRule) {
Rule annotation = getRuleAnnotation();
aRule.setId(annotation.id());
aRule.setName(annotation.name());
return aRule;
}
public AbstractRule<T> addHandler(IHandler<RuleBaseEntity> iHandler) {
if (objects.isNull(handlers)) {
handlers = new ArrayList<>();
handlers.add(iHandler);
} else {
handlers.add(iHandler);
}
return this;
}
/**
* 获取规则注解
*
* @return
*/
public abstract Rule getRuleAnnotation();
public abstract AbstractRule<T> builder();
/**
* 规则处理器handler处理事件
*/
public void performActions() {
for (IHandler<RuleBaseEntity> handler : handlers) {
handler.onEvent(EntityContextHolder.getContext());
}
}
}
规则组的定义
规则组是对规则的组合衍生的节点。一个规则组首先需要定义规则组id,这里和规则的定义道理是一样的,包括name的设计,规则组下的规则执行其实是一套策略需要提供给使用方,暴露选择权给外部,比方说这些规则是串行执行,还是并行执行 ,还是部分串行,部分并行 。亦或是规则之间需要顺序,哪个规则必须在哪个规则执行结束完成后才能执行,等等,所以我们需要提供策略包给到使用方。
这里你可以使用枚举先穷举常用写在你的组件里,另外再提供可扩展口子给到使用方。这里为了演示,我们组件中策略包只有是否并行还是串行。
再结合封装特性 ,规则组下包含规则这是一对多的模型,这时候就需要使用委派,提供对规则的添加方法。另外为了实例化方便,我们暴露关键参数给外部实例化方便。像是否并发这种字段对于组件来说是内部的语意,无须给使用者暴露,我们需要给使用者说明的是你就调这个方法,就可以实现并发执行规则,你调这个是串行执行规则,在方法名中做清晰隔离就行。另外就是执行的方法,如果满足规则条件则执行其对应的规则处理器。
csharp
@Data
public class RuleGroupDriver {
private RuleGroupDriver() {
}
/**
* 规则组ID
*/
private String ruleGroupId;
/**
* 规则组name
*/
private String ruleGroupName;
/**
* 是否并发处理
*/
private Boolean parallelInvoke;
/**
* 规则集
*/
private Set<AbstractRule> rules;
public RuleGroupDriver bindingRule(AbstractRule rule) {
if (objects.isNull(rules)) {
this.rules = new HashSet<>();
rules.add(rule);
} else {
rules.add(rule);
}
return this;
}
/**
* 实例化单线程处理规则组
*
* @param ruleGroupId
* @param ruleGroupName
*/
public static RuleGroupDriver instanceRuleDriver(String ruleGroupId, String ruleGroupName) {
RuleGroupDriver ruleGroupDriver = new RuleGroupDriver();
ruleGroupDriver.setRuleGroupId(ruleGroupId);
ruleGroupDriver.setRuleGroupName(ruleGroupName);
ruleGroupDriver.setParallelInvoke(Boolean.FALSE);
return ruleGroupDriver;
}
/**
* 实例化多线程处理规则组
*
* @param ruleGroupId
* @param ruleGroupName
*/
public static RuleGroupDriver instanceRuleDriverWithParallel(String ruleGroupId, String ruleGroupName) {
RuleGroupDriver ruleGroupDriver = new RuleGroupDriver();
ruleGroupDriver.setRuleGroupId(ruleGroupId);
ruleGroupDriver.setRuleGroupName(ruleGroupName);
ruleGroupDriver.setParallelInvoke(Boolean.TRUE);
return ruleGroupDriver;
}
/**
* 规则组处理
*/
public void process() {
if (parallelInvoke) {
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称:" + ruleGroupName + "---> 接受到处理任务]");
CountDownLatch begin = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(rules.size());
ExecutorService exec = Executors.newFixedThreadPool(rules.size());
AbstractRule[] abstractRules = rules.toArray(new AbstractRule[0]);
System.out.println("该规则组下共有" + abstractRules.length + "子规则");
for (int index = 0; index < abstractRules.length; index++) {
AbstractRule rule = abstractRules[index];
System.out.println("该规则下共有" + rule.getHandlers().size() + "子处理器");
Runnable run = () -> {
try {
begin.await();
if (rule.evaluateConditions(EntityContextHolder.getContext())) {
System.out.println("[规则Id:" + rule.getId() + "---> 规则名称:" + rule.getName() + "---> 被触发]");
rule.performActions();
System.out.println("[规则Id:" + rule.getId() + "---> 规则名称:" + rule.getName() + "---> 被成功执行]");
}
} catch (InterruptedException e) {
System.out.println("[规则Id:" + rule.getId() + "---> 规则名称:" + rule.getName() + "---> 执行异常]");
} finally {
end.countDown();
}
};
exec.submit(run);
}
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称:" + ruleGroupName + "---> 多线程开始处理]");
begin.countDown();
try {
end.await();
} catch (InterruptedException e) {
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称:" + ruleGroupName + "---> 规则组执行过程中异常中断]");
}
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称" + ruleGroupName + "---> 多线程处理完成]");
exec.shutdown();
} else {
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称:" + ruleGroupName + "---> 接受到处理任务]");
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称:" + ruleGroupName + "---> 单线程开始处理]");
System.out.println("该规则组下共有" + rules.size() + "子规则");
for (AbstractRule rule : rules) {
try {
System.out.println("该规则下共有" + rule.getHandlers().size() + "子处理器");
if (rule.evaluateConditions(EntityContextHolder.getContext())) {
System.out.println("[规则Id:" + rule.getId() + "---> 规则名称:" + rule.getName() + "---> 被触发]");
rule.performActions();
System.out.println("[规则Id:" + rule.getId() + "---> 规则名称:" + rule.getName() + "---> 被成功执行]");
}
} catch (Exception ex) {
System.out.println("[规则Id:" + rule.getId() + "---> 规则名称:" + rule.getName() + "---> 执行异常]");
}
}
System.out.println("[规则组Id:" + ruleGroupId + "---> 规则组名称:" + ruleGroupName + "---> 单线程处理完成]");
}
}
}
案例演示
我们来设计一个场景,验证下这个组件。假设现在商品发布流程有三个规则,分别是商品标题敏感词校验规则、商品营销互斥校验规则、商品库存限购校验规则。我们先简单定义一个商品类:
vbnet
@Data
public class LbItem implements Serializable {
private Long itemId;
private String itemTitle;
private Long itemBasicPrice;
private Long itemMarketingPrice;
private Integer itemStockNum;
}
具体的规则如下:
typescript
@Rule(id = "itemTitleSensitiveCheckRule", name = "商品发布规则", detail = "商品标题敏感词校验规则")
public class ItemTitleSensitiveCheckRule extends AbstractRule<LbItem> {
@Override
public boolean evaluateConditions(LbItem lbItem) {
return true;
}
@Override
public Rule getRuleAnnotation() {
return ItemTitleSensitiveCheckRule.class.getAnnotation(Rule.class);
}
@Override
public ItemTitleSensitiveCheckRule builder() {
return (ItemTitleSensitiveCheckRule) assembly(new ItemTitleSensitiveCheckRule());
}
public static ItemTitleSensitiveCheckRule build() {
ItemTitleSensitiveCheckRule checkRule = new ItemTitleSensitiveCheckRule();
return checkRule.builder();
}
}
typescript
@Rule(id = "itemStockNumQuotaCheckRule", name = "商品发布规则", detail = "商品库存限购校验规则")
public class ItemStockNumQuotaCheckRule extends AbstractRule<LbItem> {
@Override
public boolean evaluateConditions(LbItem lbItem) {
return true;
}
@Override
public Rule getRuleAnnotation() {
return ItemStockNumQuotaCheckRule.class.getAnnotation(Rule.class);
}
@Override
public ItemStockNumQuotaCheckRule builder() {
return (ItemStockNumQuotaCheckRule) assembly(new ItemStockNumQuotaCheckRule());
}
public static ItemStockNumQuotaCheckRule build() {
ItemStockNumQuotaCheckRule checkRule = new ItemStockNumQuotaCheckRule();
return checkRule.builder();
}
}
typescript
@Rule(id = "itemMarketingLimitCheckRule", name = "商品发布规则", detail = "商品营销互斥校验规则")
public class ItemMarketingLimitCheckRule extends AbstractRule<LbItem> {
@Override
public boolean evaluateConditions(LbItem lbItem) {
return false;
}
@Override
public Rule getRuleAnnotation() {
return ItemMarketingLimitCheckRule.class.getAnnotation(Rule.class);
}
@Override
public ItemMarketingLimitCheckRule builder() {
return (ItemMarketingLimitCheckRule) assembly(new ItemMarketingLimitCheckRule());
}
public static ItemMarketingLimitCheckRule build() {
ItemMarketingLimitCheckRule checkRule = new ItemMarketingLimitCheckRule();
return checkRule.builder();
}
}
规则处理器定义如下:
typescript
public class ItemTitleSensitiveRiskHandler implements IHandler<RuleBaseEntity> {
@Override
public void onEvent(RuleBaseEntity t) {
//do your biz logic 做你的业务逻辑
System.out.println("风控检查商品标题是否含有敏感词");
}
}
typescript
public class ItemCategoryRecommendHandler implements IHandler<RuleBaseEntity> {
@Override
public void onEvent(RuleBaseEntity order) {
//do your biz logic 做你的业务逻辑
System.out.println("检查商品类目推荐匹配");
}
}
typescript
public class ItemOtherMoreInfoHandler implements IHandler<RuleBaseEntity> {
@Override
public void onEvent(RuleBaseEntity t) {
//do your biz logic 做你的业务逻辑
System.out.println("结合业务,更多的检查处理器");
}
}
ok,准备工作一切就绪,让我们来测试一下吧。
typescript
public class Client {
public static void main(String[] args) {
// 单个规则组测试
singleRuleGroupTest();
// 给大家留下的作业
multiRuleGroupTest();
}
private static void multiRuleGroupTest() {
// 快来try try coding
}
private static void singleRuleGroupTest() {
String ruleGroupId = "1";
String ruleGroupName = "商品发布规则组";
RuleGroupDriver ruleGroupDriver = RuleGroupDriver.instanceRuleDriver(ruleGroupId, ruleGroupName);
ItemTitleSensitiveCheckRule rule1 = ItemTitleSensitiveCheckRule.build();
rule1.addHandler(new ItemTitleSensitiveRiskHandler())
.addHandler(new ItemCategoryRecommendHandler())
.addHandler(new ItemOtherMoreInfoHandler());
//如法炮制 etc..
//ItemStockNumQuotaCheckRule rule2 = ItemStockNumQuotaCheckRule.build();
ruleGroupDriver.bindingRule(rule1);
ruleGroupDriver.process();
}
}

这套组件就分享到这里了,有疑问的同学可以结合源码实现反复看几遍。组件设计过程是如何一步步思考的,建议把这些问题都搞懂,优秀的设计和理解能力对于开发是比较重要的~