规则引擎,是处理一系列规则集合的引擎。通过输入一些参数,经过指定的规则,从而实现想要的结果。简单的一个理解就是when符合一定规则(条件),then做对应的业务逻辑处理。目的是将业务决策与实际的业务逻辑解耦。通过灵活地增加或删除指定的规则,可以实现良好的可扩展性。
1、背景:
公司主营港口无人驾驶,我负责云端,当公司拿下一个港口的运营订单后,就需要云端去和港口的自动化设备进行对接,选择的做法就是新建一个服务,专门用来做对接,且不论不同港口自动化设备是否由一个厂家生产,又或者协议报文是否一致,我们都需要重新开发,一方面完成与自动化设备的交互,一方面根据业务上的要求,给无人车发送一些指令,或者做一些其它业务处理。这种做法一方面不符合DRY(don't repeat yourself)设计原则,另一方面增加了额外的人力和时间成本,浪费资源。
所以,计划实现一个简单的规则引擎,放在一个统一的模块中,每个港口只需要单独对接一下各个港口的协议和数据,然后转换成一个标准的输出给到规则引擎所在的模块即可,业务的决策和执行由规则引擎统一调度。要求是,期望这个规则引擎可以应对绝大多数业务场景,当前规则引擎希望覆盖自动化作业场景(例如岸桥、场桥自动化交互业务场景)。
2、实现:
定义一个标准的输入输出实体JobContext,用作统一的规则引擎的输入以及其它模块的输出。以自动化作业业务为例,定义一个自动化作业业务的实体EcsJobContext,相关的参数封装在一个名为EcsDescription的实体中,EcsJobContext组合EcsDescription。
2.1、JobContext
标准的输入输出实体
public abstract class JobContext {
}
2.2、EcsJobContext
自动化作业业务的实体
public class EcsJobContext extends JobContext {
private ECSDescription ecsDescription;
}
2.3、EcsDescription
实体参数
public class ECSDescription {
/**
* 桥吊编号
**/
private String craneCode;
/**
* 堆场号
**/
private String yardId;
/**
* 贝位号
**/
private String bayId;
/**
* 吊具尺寸
**/
private String spreaderSize;
/**
* 对位值
**/
private double cpsValue;
// 省略其它字段,例如自动化设备的位置等等
}
定义一个规则接口JobRule,其中包含一个when方法,一个then方法,表示when满足条件,then执行对应业务逻辑。针对不同类型的规则,进行若干层的抽象,比如,有简单规则类型SimpleRule、复合规则类型CompositeJobRule、串行执行的规则类型SequentialCompositeRule、并行执行的规则类型ParallelCompositeRule等等。
2.4、JobRule
public interface JobRule<T extends JobContext> {
boolean when(T jobContext);
void then(T jobContext);
}
2.5、SimpleRule
SimpleRule表示一般的单条规则,即满足when,执行then。
public abstract class SimpleRule<T extends JobContext> implements JobRule<T> {
@Override
public boolean when(T jobContext) {
return false;
}
@Override
public void then(T jobContext) {
}
}
2.6、CompositeJobRule
CompositeJobRule表示一系列的规则,任意组合,只要是规则即可。
public abstract class CompositeJobRule<T extends JobContext> implements JobRule<T> {
public abstract List<JobRule<T>> getRules(T jobContext);
public abstract boolean when(T jobContext);
public abstract void then(T jobContext);
}
2.7、SequentialCompositeRule
SequentialCompositeRule表示需要顺序执行的一系列规则。
public abstract class SequentialCompositeRule<T extends JobContext> extends CompositeJobRule<T> {
@Override
public boolean when(T jobContext) {
return true;
}
@Override
public void then(T jobContext) {
this.getRules(jobContext).forEach(rule -> {
if (rule.when(jobContext)) {
rule.then(jobContext);
}
});
}
}
2.8、AlwaysRunSimpleRule
AlwaysRunSimpleRule表示一定会执行的规则,即when条件永远是true
public abstract class AlwaysRunSimpleRule<T extends JobContext> extends SimpleRule<T> {
@Override
public boolean when(T jobContext) {
return true;
}
}
定义一个规则引擎的接口JobEngine,用来调度使用的规则。项目上可以存在多个规则引擎,以岸桥相关业务来讲,定义一个岸桥业务的规则引擎EcsQcJobEngine用来实现岸桥业务。
2.9、JobEngine
public abstract class JobEngine<T extends JobContext> {
public void handleJob(T jobContext) {
getJobRules().forEach(rule -> {
try {
if (rule.when(jobContext)) {
rule.then(jobContext);
}
} catch (Exception e) {
log.error("规则引擎处理失败! error: {}", ExceptionUtil.getSimpleStackTrace(e));
}
});
}
protected abstract List<JobRule<T>> getJobRules();
}
2.10、EcsQcJobEngine
岸桥业务的规则引擎,其中QcInfoHandlerRule是一个复合类型规则,且顺序执行即可。
public class EcsQcJobEngine extends JobEngine<EcsJobContext> {
@Resource
private QcInfoHandlerRule qcInfoHandlerRule;
@Override
protected List<JobRule<EcsJobContext>> getJobRules() {
return Lists.newArrayList(qcInfoHandlerRule);
}
}
2.11、QcInfoHandlerRule
表示岸桥业务相关的规则,里面是一系列岸桥业务相关规则,这些规则可以是任意定义好的规则类型,可以根据业务需要增加或删除对应的规则,扩展性较好,且针对任一规则的修改,不影响其它业务,也比较符合OCP原则。例如其中QcAlignRule就是岸桥对准相关业务,QcClosedLaneRule是岸桥关路相关业务,QcPositionChangeRule是岸桥位置变更相关业务等,他们可以根据各自是否满足各自when条件来决策是否执行但对应的业务。
public class QcInfoHandlerRule extends SequentialCompositeRule<EcsJobContext> {
private final List<JobRule<EcsJobContext>> rules;
public QcInfoHandlerRule(QcAlignRule qcAlignRule, QcClosedLaneRule qcClosedLaneRule,
QcIdAndLaneIdMappingRule qcIdAndLaneIdMappingRule, QcPositionSaveRule qcPositionSaveRule,
QcPositionChangeRule qcPositionChangeRule, QcSpreaderSizeChangeRule qcSpreaderSizeChangeRule,
QcSpreaderInfoSaveRule qcSpreaderInfoSaveRule) {
rules = ImmutableList.of(qcAlignRule, qcClosedLaneRule, qcIdAndLaneIdMappingRule, qcPositionSaveRule, qcPositionChangeRule, qcSpreaderSizeChangeRule, qcSpreaderInfoSaveRule);
}
@Override
public List<JobRule<EcsJobContext>> getRules(EcsJobContext jobContext) {
return rules;
}
}
例如QcAlignRule,岸桥对准业务的规则,是一个一定会执行的规则,那么它的实现如下:
public class QcAlignRule extends AlwaysRunSimpleRule<EcsJobContext> {
// 岸桥对准相关业务
@Resource
protected ECSAlignService ecsAlignService;
@Override
public void then(EcsJobContext jobContext) {
ecsAlignService.align(jobContext.getEcsDescription());
}
}
再例如QcPositionChangeRule,岸桥位置变更的业务,是否执行对应的业务是要进行判断的,那么它的实现如下:
public class QcPositionChangeRule extends SimpleRule<EcsJobContext> {
@Resource
private WFJZXComponent wfjzxComponent;
@Override
public boolean when(EcsJobContext jobContext) {
ECSDescription ecsDescription = jobContext.getEcsDescription();
if (ecsDescription.isHatchCoverMode()) {
return false;
}
if (ecsDescription.isCartPositionLost()) {
log.info("设备{}大车位置距离丢失!", ecsDescription.getCraneCode());
// 省略一点处理
return false;
}
return true;
}
@Override
public void then(EcsJobContext jobContext) {
// 省略具体的业务处理
}
}
最终,只需要调用方调用规则引擎提供的接口,将EcsDescription这一自动化作业业务实体参数传递到规则引擎,那么后续的处理就都交由规则引擎处理了,至此,岸桥业务的处理就变得通用起来了,后续新增港口,只需要将参数封装好即可。
public void distributeCommand(ECSDescription ecsDescription) {
ecsQcJobEngine.handleJob(new EcsJobContext(ecsDescription));
}
3、总结:
规则引擎也是一种解耦的设计思想,在规则引擎的世界里,一切皆规则,将业务的决策和实现分离,且由于规则的拆分,使得单个规则的维护成本也大大降低,使用规则引擎也避免了超大类的出现以及业务逻辑相互耦合严重的问题。本文规则引擎的实现比较简单,但是足够满足业务使用和扩展了,有些复杂的业务引擎还会涉及到不修改代码从而动态添加规则的操作,美团技术博客对于规则引擎的相关文章可以了解一下。