像Drools这样的规则引擎通常使用自定义语言来定义一组规则。 例如,Drools编译器将drl文件转换为内部表示形式(KiePackages),该内部表示形式随后用于生成ReteOO / Phreak网络,该网络将执行规则评估。
这种内部表示从未真正打算由最终用户生成或使用。 这种复杂性使得很难以编程方式编写规则,而建议是在DRL级别上生成文本规则。 这意味着drl本身是当前在Drools中定义一组规则的唯一实用形式符号。
当时,流口水的内部构造基于几个假设,这些假设不再是正确的或不可取的。 在Java 8之前,需要考虑perm gen,因此使用了各种解决方案来解决这一问题-例如基于反射的MVEL。 Java 8现在将代码放在堆上,因此不再需要。 在引擎级别,它还检查并生成了索引,并将其与DRL生成的表达式相关联–这使多语言无法实现。 最后,它自由地使用类加载器和反射,这使得在不同环境下执行执行时很难进行编译。
引擎独立规则模型
为了克服此限制并提供以编程方式在纯Java中定义一组规则的可能性,我们开发了一种旨在提供规则集和规则集规范表示的模型。
流利的DSL可以方便地创建此模型的实例。 该模型本身完全独立于Drools,在理论上可以被其他引擎重用。 它还引入了将引擎与必须了解任何语言完全分开的层。 例如,它不会检查和生成索引,而是希望从上面的层提供这些索引。 另一个优势意味着,现在Drools对其执行的内容具有开发人员友好的看法,因为它们全都只是纯粹的低级pojo规则。
除了为所有Java开发人员提供一种以纯Java编写规则的干净方法之外,该模型还将使我们的团队能够更快地尝试新功能,从而使我们摆脱了实现集成新功能的相应解析器和编译器部件的负担。具有drl表示法的功能。
让我们来看一个使用上述DSL定义的规则模型的实际示例:
Rule rule = rule( "Persons older than Mark" )
.view(
expr("exprA", markV, p -> p.getName().equals("Mark"))
.indexedBy( String.class, ConstraintType.EQUAL, Person::getName, "Mark" )
.reactOn( "name", "age" ),
expr("exprB", olderV, p -> !p.getName().equals("Mark"))
.indexedBy( String.class, ConstraintType.NOT_EQUAL, Person::getName, "Mark" )
.reactOn( "name" ),
expr("exprC", olderV, markV, (p1, p2) -> p1.getAge() > p2.getAge())
.indexedBy( int.class, ConstraintType.GREATER_THAN, Person::getAge, Person::getAge )
.reactOn( "age" )
)
.then(on(olderV, markV)
.execute((p1, p2) -> System.out.println( p1.getName() + " is older than " + p2.getName())));
这等效于drl中表达的以下规则:
rule "Persons older than Mark" when
$p1 : Person(name == \"Mark\")
$p2 : Person(name != \"Mark\", age > $p1.age)
then
System.out.println($p2.getName() + \" is older than \" + $p1.getName());
end
显然,DSL版本更加冗长,并且需要指定更多详细信息,反之,它们由drools编译器在解析用drl编写的规则时推断出。
正如预期的那样,这样做是有意为之的,因为模型必须明确包含生成ReteOO / Phreak网络的一部分以评估该规则所需的所有信息,而无需执行任何字节码检查或使用任何其他复杂,易碎和非便携式自省技术。 在那些简单的示例中,索引包含与expr相同的逻辑,因为每个DRL表达式可以包含的不仅仅是索引。 索引将由较高的语言层推断并隐式添加。
为了阐明这一方面,让我们更详细地分析单个LHS约束:
expr("exprA", [1]
markV, [2]
p -> p.getName().equals("Mark") ) [3]
.indexedBy( String.class, ConstraintType.EQUAL, Person::getName, "Mark" ) [4]
.reactOn( "name", "age" ) [5]
在此语句中,您可以注意到以下部分:
[1]这是用于确定其身份的约束的标签。
两个相同的约束必须具有相同的标签,而两个不同的约束必须具有不同的标签,以使引擎在可能的情况下正确实现节点共享。
可以选择省略此标签,在这种情况下,它将通过对实现约束的lambda表达式的字节码进行自动自省生成。
但是,正如预期的那样,最好避免使用任何自省机制,然后在可能的情况下显式提供约束标签。
[2]这是在创建规则之前定义的变量,用于将实际事实与用于评估条件的lambda表达式的形式参数绑定在一起。
它等效于用drl表示法编写的规则中的变量声明。
[3]执行约束评估的lambda表达式。
[4]此约束类型的规范以及引擎必须如何对其进行索引。
[5]此约束必须为反应性的Person对象的属性名称。
建立并执行模型
您可以通过编程将规则库的此规则和其他规则添加到模型中:
Model model = new ModelImpl().addRule( rule );
一旦有了这个完全独立于Drools算法和数据结构的模型,您就可以使用第二个项目 ,这次它既依赖于Drools,也依赖于模型本身,从而用它创建一个KieBase。
KieBase kieBase = KieBaseBuilder.createKieBaseFromModel( model );
该构建器在内部所做的是从模型中重新创建KiePackages,然后使用现有的drools编译器进行构建。 生成的KieBase与通过编译相应的drl可以获得的KieBase相同,然后可以以完全相同的方式使用它,从中创建一个KieSession并用您的域事实填充它。
在这里,您可以找到更多测试用例,以显示该模型当前支持的Drools功能以及允许使用它们的DSL构造,而在这里您可以找到一个更完整的示例。
将可执行模型嵌入到kjar中
目前,一个kjar包含一些实现约束和后果的预生成类。 但是,所有drl文件都需要从头开始进行解析和编译,因此仅从源代码构建文件时就节省了有限的钱。 可执行模型也将有助于加快此过程。 将实现规则库模型的类嵌入到kjar中,可以快速重新创建KiePackages,而不必从drl文件重新启动整个编译过程。 提供了有关如何工作的证明概念
在这里 。
DRL编译器到可执行模型
我们正在开发一个新的编译器,它将使用现有的DRL并直接输出可执行模型,该模型存储在kjar中。 这将导致更快的加载和构建时间。 通过在kjar中预缓存更多信息,我们也正在寻找其他加快此过程的方法,这些信息可以在最初的kjar构建期间确定。
Pojo,Polyglot和DRLX规则
可执行模型是低级的,并且由于您必须提供其所需的所有信息,因此有点冗长。 这是通过设计完成的,以支持语言级别的创新。 尽管从技术上讲它仍然是规范性规则,但日常使用并不理想。 在不久的将来,我们将在不太冗长的pojo规则层上工作,该层将使用注释处理器注入诸如index和reactOn之类的东西。 我们还希望它会产生多种规则语言,而不仅仅是一种-scala规则,闭包规则,mini-dsl规则等。对于一种语言也没有必要公开所有引擎功能,人们可以编写mini-dsls公开子集。
最后,我们还在研究下一代DRL语言(DRLX),它将是Java的超集。 它仍处于设计阶段,并且将在一段时间内不可用,但是,一旦早期的规范草案准备就绪,我们将为此发布一些建议。
翻译自: https://www.javacodegeeks.com/2017/08/drools-canonical-model-pure-java-rules.html