写点什么

转转图书对基于 Drools 引擎的 DMN 实践

  • 2022-11-02
    北京
  • 本文字数:4755 字

    阅读完需:约 16 分钟

转转图书对基于Drools引擎的DMN实践

1 背景介绍

1.1 DMN 是什么

DMN 全称 Decision Model and Notation(决策模型和符号、决策模型和表示法),是一种用于表示业务决策和规则的规范,旨在帮助参与决策的人都能简单快速理解决策过程。



DMN 是由 OMG(Object Management Group,对象管理组织)管理的一种规范,该组织下比较知名的还有 UML 等。



如图所示,DMN 的表现形式近似于流程图,通过可视化来更直观地体现出流转和处理的过程,而各个节点使用附带逻辑的表格来表现该节点处理数据的方式。


由于 DMN 是一个规范,所以在应用上,主要依靠各个 DMN 工具供应商提供具体实现,近似于 SQL 语句和 MySQL、Oracle 的关系。

1.2 为什么要用 DMN

应用 DMN 主要目的是为了解决转转图书项目定价逻辑过于复杂的问题。


在引入 DMN 之前,图书项目使用 java 代码实现产品人员提供的定价逻辑。但是随着逻辑越来越复杂,定价逻辑的代码也越来越难以阅读和维护。同时,将产品人员的逻辑翻译成代码是一个单向过程,只有程序员能理解实现过程,产品人员只能通过结果反推是否正确合理,在逻辑变得复杂之后,很难快速发现问题。



而通过 DMN,可以可视化的表示出逻辑过程,产品人员也可以直观的看到是否符合预期,还能够进行编辑,从而解决了只有程序员掌握实现而过于依赖程序员的问题,也能减少表述交流的过程产生理解偏差进而导致的错误。


1.3 什么是 Drools

Drools 是用 java 语言编写的开源规则引擎,是 DMN 规范的一种实现。举例来说 DMN 规范就是接口,Drools 就是实现了接口的其中一个工具类。



Drools 属于 Kie 开源社区,而 kie 社区由 Red Hat 赞助,有比较高的社区活跃度。


1.4 为什么选择 Drools

Drools 引擎是基于 java 实现的,这使得不需要为 Drools 单独部署运行环境,运维成本是 0,十分友好。


其次,DMN 规范规定实现其规范的软件必须满足三级递增的符合性级别,三级最高,一级最低,满足三级级别时必须同时满足一级和二级级别。而 Drools 引擎对 DMN 规范的支持属于三级级别,功能完善。



同时,Drools 引擎社区活跃度高,同时提供了完整的工作组件,可靠性高。

2 Drools 引擎应用

以下内容基于 7.61.0 版本,与 8.x 及以上的版本存在差异

2.1 官方推荐的最直接的应用方式

首先需要部署运行一个Kie Server用来执行 DMN 规则,然后部署 Drools 官方提供的基于 Web 的工作站软件用来编辑、测试、发布 DMN 规则。



当部署完成 DMN 规则之后,可以通过Kie Server REST API来运行规则获取结果,即通过 Http 的方式请求Kie Server提供的接口。

2.2 转转图书的限制

首先说图书项目并没有直接按照官方推荐的做法部署应用,原因有几个方面:


第一点,转转内部项目部署运行有自有的框架体系,而Kie Server是基于JBoss运行的服务器软件,在现有体系中部署一个外部项目需要额外的运维成本。


其次,Kie Server作为一个第三方工具,当出现问题时仅靠图书项目的人员难以解决,而等待社区反馈对于转转图书这种线上的商业项目难以接受。所以为了尽可能的减少出现问题的概率,节约人力,图书项目倾向于尽可能少的引入外部依赖。


基于以上原因,图书项目在应用 Drools 的时候选择了另外一种方式。

3 脱离 Kie Server 的 Drools 引擎实践

脱离开Kie Server,自然就没有了REST API的 http 接口,同时也失去了官方工作站的支持,但是相对的,Drools 提供了一些对这种场景下的支持。

3.1 在线编辑 DMN 规则

在 Kie 旗下有另一款名为Kogito的产品,是一个提供在线编辑 BPMN 和 DMN 的服务器软件,同时其中有一个all-in-one的 js 文件,实现了在线编辑 DMN 的全部功能。



图书项目基于这个 js 文件进行了包装,增加易用性,使得 DMN 编辑功能融入到现有的工作后台之中。



同时增加编辑记录列表用来便于管理和回滚。



js 工具编辑后的 DMN 规则内容是 xml 格式的字符串,可以使用 js 提供的getContent()接口导出。

3.2 使用 Drools 引擎执行 DMN 规则

没有了Kie Server的支持之后,需要通过代码的方式运行 Drools 引擎。


首先在项目中引入 Drools 引擎的组件



然后在项目中创建 Drools 引擎的引用并执行。


// 初始化KieServicesKieServices kieServices = KieServices.Factory.get();// 通过指定的maven依赖创建Kie容器ReleaseId releaseId = kieServices.newReleaseId( "com", "my-kjar", "1.0.0" );KieContainer kieContainer = kieServices.newKieContainer( releaseId );// 通过容器获取DMN运行时DMNRuntime dmnRuntime = KieRuntimeFactory.of(kieContainer.getKieBase()).get(DMNRuntime.class);// 通过DMN运行时获取需要执行的DMNModel,其中包含了DMN规则String namespace = "my-namespace";String modelName = "dmn-model-name";DMNModel dmnModel = dmnRuntime.getModel(namespace, modelName);// 获取上下文,并传入DMN规则需要用到的数据DMNContext dmnContext = dmnRuntime.newContext();dmnContext.set("inputData", "123");  // 执行规则获取结果DMNResult dmnResult = dmnRuntime.evaluateAll(dmnModel, dmnContext);  for (DMNDecisionResult dr : dmnResult.getDecisionResults()) {      log.info("Decision: '" + dr.getDecisionName() + "', " + "Result: " + dr.getResult());}
复制代码


由于 Kie 容器使用 maven 作为读取 DMN 配置的手段,所以要求 DMN 规则内容需要打包到 Kie 容器能够识别的 jar 包里,并且部署到项目可访问的 maven 环境中。

3.3 完善处理流程

按照上述流程就可以实现脱离Kie server运行 Drools 引擎。但是距离落地上线还有一小点距离。


官方的工作站可以提供 DMN 规则上线前的验证和测试功能,但是all-in-one的 js 文件没有,所以为了保证准确性和稳定性,需要额外实现 DMN 规则的验证动作。


这里可以使用KieContainer提供的verify接口来触发 Drools 引擎的验证动作并获取结果。


Results verify = kieContainer.verify();for (Message message : verify.getMessages()) {    log.info(message.getLevel().name() + ": " + message.getText());}
复制代码


将验证流程放到保存 DMN 规则的时候,就可以快速发现是否存在编写错误。

3.4 DLC - 脱离 maven 环境运行 Drools 引擎

前面说了 Drools 引擎需要使用 maven 获取运行的 DMNjar 文件,但是在实际应用中,线上环境不一定会部署 maven。例如图书项目需要在公司内部公用的 spark 集群上运行 Drools,但是 spark 集群上没有部署 maven 环境,按照上述流程运行就会报错。


所以需要一个能够使 Drools 脱离 maven 环境的手段。


通过分析源码可以发现,在创建KieContainer的时候,会使用KieServices.getRepository()方法获取数据源,并通过 maven 坐标查找其中的KieModule。所以需要做的就是直接我们需要的 jar 写入KieServices的数据源中:


// 首先通过JarOutputStream生成包含DMN规则字符串,且符合Drools规范的jar二进制流ByteArrayOutputStream outputStream = new ByteArrayOutputStream();    JarOutputStream jos = new JarOutputStream(outputStream);    // kmodule.xml    jos.putNextEntry(new JarEntry("/META-INF/kmodule.xml"));    jos.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><kmodule xmlns=\"http://www.drools.org/xsd/kmodule\"><kbase name=\"kbase_1\" packages=\"rules\" default=\"true\"><ksession name=\"ksession_1\" default=\"true\"/></kbase></kmodule>".getBytes(StandardCharsets.UTF_8));    // pom.properties    jos.putNextEntry(new JarEntry("/META-INF/maven/com.myspace/test/pom.properties"));    jos.write("groupId=com.myspace\nartifactId=test\nversion=1.0.0".getBytes(StandardCharsets.UTF_8));    // pom.xml    jos.putNextEntry(new JarEntry("/META-INF/maven/com.myspace/test/pom.xml"));    jos.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">  <modelVersion>4.0.0</modelVersion>  <groupId>com.myspace</groupId>  <artifactId>test</artifactId>  <version>1.0.0</version>  <packaging>kjar</packaging>  <name>test</name>  <description></description></project>".getBytes(StandardCharsets.UTF_8));    // dmn    jos.putNextEntry(new JarEntry("/config/rules/test.dmn"));    jos.write(dmn.getBytes(StandardCharsets.UTF_8));    jos.finish();ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());// 初始化KieServicesKieServices kieServices = KieServices.Factory.get();// 获取KieServices的数据源KieRepository repository = kieServices.getRepository();// 向数据源中加入基于生成二进制流构造的KeiModuleKieModule kieModule = repository.addKieModule(kieServices.getResources().newInputStreamResource(inputStream));
复制代码


如上就可以实现在没有 maven 环境的时候仍然能够让KieServices获取到我们需要 DMN 规则。


但是仅仅如此还不够,在创建KieContainer的时候,内部会生成一个KieProject实例,KieProject在实例化的过程中会默认生成一个基于 maven 的MavenClassLoaderResolver用于查找 jar,而缺少 maven 环境的情况下在生成MavenClassLoaderResolver的时候也会报错。为此,我们需要一个取代MavenClassLoaderResolver的办法。


通过分析 Drools 源码可以发现,在源码中,某些时候会使用ProjectClassLoader.findParentClassLoader()来获取基于当前运行环境的ClassLoader


所以,需要在创建KieContainer的时候使用ProjectClassLoader.findParentClassLoader()生成的ClassLoader来取代默认的MavenClassLoaderResolver:


// 使用另外一个接收ClassLoader的接口来生成KieContainerKieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId(), ProjectClassLoader.findParentClassLoader());
复制代码


如此一来,就可以完全脱离 maven 环境来使用 Drools 引擎了,而 Drools 引擎也可以融入到任意的 java 项目之中,部署到任意的 java 环境之下。

4 总结

DMN 在实际应用中有比较明显的优点:


  • 可以在不修改代码的情况下更新逻辑,减少上线过程中可能产生的问题

  • 可以通过一套代码实现各种场景的需求

  • 流程可视化,便于理解逻辑的运行

  • 能够让一部分不熟悉程序代码的人员也可以参与编辑,避免流程全部只由程序员了解


但是相对的也存在一些弊端或者表现不够好的地方:


  • 流程复杂时图形化表现比较杂乱,尤其是多个决策节点依赖相同的上游时

  • 编辑规则时还是会有一部分场景条件需要写代码才能实现,例如列表包含(可以使用 DMN 专用代码 FEEL 实现,或者引用已经实现的 java 方法实现)

  • 运行规则的时间相较于使用 java 代码实现要慢,因为中间涉及规则文件解析和各个节点的计算,而 java 代码可以更直接的实现



总体而言,DMN 主要应用在向程序员以外的人员提供决策管理的能力,以求更准确地反映目的,从程序员的角度讲可能和写 ifelse 没什么不一样,但是其他角色的参与人员可以通过较低的学习成本来上手实现规则,能够减少沟通成本和不同人的理解差异产生的不符合预期的结果。

5 参考资料

Drools 官方网站:https://www.drools.org/


Drools 官方的 DMN 教学:https://www.drools.org/learn/dmn.html


Drools 官方的 15 分钟简易教学:https://learn-dmn-in-15-minutes.com/learn/introduction


Kogito 在线编辑网页:https://sandbox.kie.org/#/


Kie 官方网站:https://www.kie.org/


OMG 官方网站的 DMN 页:https://www.omg.org/dmn/


作者简介

项赢,转转资深 java 工程师。长期服务于转转图书项目。


转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。关注公众号「转转技术」(综合性)、「大转转 FE」(专注于 FE)、「转转 QA」(专注于 QA),更多干货实践,欢迎交流分享~

发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2019-04-30 加入

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 关注公众号「转转技术」,各种干货实践,欢迎交流分享~

评论

发布
暂无评论
转转图书对基于Drools引擎的DMN实践_drools_转转技术团队_InfoQ写作社区