当前位置: 首页 > 科技观察

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

时间:2023-03-21 18:26:57 科技观察

1背景介绍1.1什么是DMN?DMN的全称是DecisionModelandNotation(决策模型和符号,decisionmodelandnotation)。规范旨在帮助参与决策的每个人简单快速地理解决策过程。DMN标识DMN是由OMG(ObjectManagementGroup,对象管理组织)管理的规范,而UML在该组织下广为人知。DMN示例如图所示。DMN的形式类似于流程图,可以通过可视化更直观地反映流程和处理过程,每个节点用一个有逻辑的表格来表示节点处理数据的方式。由于DMN是一个规范,在应用方面,主要依赖于各个DMN工具供应商提供具体的实现,类似于SQL语句与MySQL、Oracle的关系。1.2为什么使用DMN应用DMN的主要目的是解决转转图书项目定价逻辑过于复杂的问题。在引入DMN之前,本书项目使用java代码来实现产品人员提供的定价逻辑。但是随着逻辑变得越来越复杂,定价逻辑的代码变得更难阅读和维护。同时,将产品人员的逻辑翻译成代码是一个单向的过程。实现过程只有程序员才能看懂,产品人员只能通过结果来推断是否正确合理。逻辑变得复杂后,很难快速发现问题。一部分复杂的规则、区间、系数可以通过DMN直观的表示逻辑过程,产品人员也可以直观的看到是否符合预期,也可以进行编辑,解决了过于依赖程序员的问题掌握执行也可以减少在表达和交流过程中因理解偏差而导致的错误。1.3.什么是流口水?Drools是一个用java语言编写的开源规则引擎,是DMN规范的一个实现。比如DMN规范就是一个接口,Drools就是其中一个实现接口的工具类。DroolslogoDrools属于Kie开源社区,kie社区由RedHat赞助,社区活跃度比较高。Kie'sproducts1.4为什么选择DroolsDrools引擎基于java实现,无需为Drools单独部署运行环境,运维成本为0,非常友好。其次,DMN规范规定,实现其规范的软件必须满足三级增量合规级别,第三级最高,第一级最低。当第三层满足时,第一层和第二层必须同时满足。Drools引擎对DMN规范的支持属于第三级别,功能完备。Drools引擎为各种版本的DMN提供支持。同时,Drools引擎社区非常活跃,提供完整的高可靠性工作组件。2Drools引擎应用以下内容基于7.61.0版本,区别于8.x及以上版本2.1官方推荐的最直接的应用方法首先需要部署运行一个KieServer来执行DMN规则,以及然后部署Drools官方提供的基于Web的工作站软件,用于编辑、测试和发布DMN规则。部署DMN规则后,Drools提供的工作站软件可以通过KieServerRESTAPI运行规则获取结果,即通过Http请求KieServer提供的接口。2.2转转书籍的限制首先,书籍项目并没有按照官方推荐直接部署应用。有几个原因:首先,转转的内部项目部署有自己的框架体系,而KieServer是基于JBoss运行的服务器软件,在现有系统中部署外部项目需要额外的运维成本。其次,KieServer作为第三方工具,出现问题时很难仅靠图书项目的工作人员解决,对于转转图书这样的在线商业项目来说,等待社区反馈也比较困难。因此,为了尽可能降低出现问题的概率和节省人力,书籍项目往往会引入尽可能少的外部依赖。基于以上原因,本书项目在应用Drools时选择了另一种方式。3没有KieServer的Drools引擎实践没有KieServer,自然也就没有RESTAPI的http接口,也失去了官方工作站的支持,不过相对来说,Drools对这种场景提供了一定的支持。3.1DMN规则的在线编辑Kie旗下还有一个产品叫Kogito,这是一款提供BPMN和DMN在线编辑的服务器软件。同时还有一个一体的js文件,实现了DMN的在线编辑。完整的功能。js提供的界面书工程就是基于这个js文件打包的,增加了易用性,将DMN编辑功能集成到现有的工作后台。包裹页面,下面是js提供的功能区,添加编辑记录列表,方便管理和回滚。编辑列表js工具编辑的DMN规则内容是一个xml格式的字符串,可以使用js提供的getContent()接口导出。3.2使用Drools引擎执行DMN规则没有KieServer的支持,需要通过代码来运行Drools引擎。首先在项目中引入Drools引擎组件,然后在项目中创建对Drools引擎的引用并执行。//初始化KieServicesKieServiceskieServices=KieServices.Factory.get();//通过指定的maven依赖创建Kie容器ReleaseIdreleaseId=kieServices.newReleaseId("com","my-kjar","1.0.0");KieContainerkieContainer=kieServices.newKieContainer(releaseId);//通过容器获取DMN运行时的DMNRuntimedmnRuntime=KieRuntimeFactory.of(kieContainer.getKieBase()).get(DMNRuntime.class);//通过获取需要执行的DMNModelDMN运行时,其中包含DMN规则Stringnamespace="my-namespace";StringmodelName="dmn-model-name";DMNModeldmnModel=dmnRuntime.getModel(namespace,modelName);//获取上下文并传入DMN需要使用的规则DataDMNContextdmnContext=dmnRuntime.newContext();dmnContext.set("输入数据","123");//执行规则获取结果DMNResultdmnResult=dmnRuntime.evaluateAll(dmnModel,dmnContext);for(DMNDecisionResultdr:dmnResult.getDecisionResults()){log.info("Decision:'"+dr.getDecisionName()+"',"+"Result:"+dr.getResult());}自Kie容器使用maven作为读取DMN配置的手段,因此需要将DMN规则的内容打包成Kie容器可以识别的jar包,部署到项目可以访问的maven环境中。3.3改进处理流程按照以上流程,Drools引擎可以在没有Kie服务器的情况下运行。但是距离登陆线上还有一点距离。官方工作站可以提供DMN规则上线前的验证测试功能,而一体机js文件没有,所以为了保证准确性和稳定性,需要额外实现DMN规则的验证动作.这里可以使用KieContainer提供的验证接口来触发Drools引擎的验证动作并获取结果。结果verify=kieContainer.verify();for(Messagemessage:verify.getMessages()){log.info(message.getLevel().name()+":"+message.getText());}将验证flow保存DMN规则时,可以快速发现是否有写错。3.4DLC——在没有maven环境的情况下运行Drools引擎前面提到Drools引擎需要使用maven来获取运行的DMNjar文件,但是在实际应用中,线上环境可能并没有部署maven。比如本书项目需要在公司内部公共spark集群上运行Drools,但是spark集群上并没有部署maven环境,按照上面的流程运行会报错。所以我们需要一种手段让Drools脱离maven环境。通过分析源码可以发现,在创建KieContainer时,会使用KieServices.getRepository()方法获取数据源,通过maven坐标找到其中的KieModule。那么我们要做的就是直接将我们需要的jar写入到KieServices的数据源中://首先通过JarOutputStreamByteArrayOutputStreamoutputStream=newByteArrayOutputStream()生成一个包含DMN规则字符串并且符合Drools规范的jar二进制流;JarOutputStreamjos=newJarOutputStream(outputStream);//kmodule.xmljos.putNextEntry(newJarEntry("/META-INF/kmodule.xml"));jos.write(""。getBytes(StandardCharsets.UTF_8));//pom.propertiesjos.putNextEntry(newJarEntry("/META-INF/maven/com.myspace/test/pom.properties"));jos.write("groupId=com.myspace\nartifactId=test\nversinotallow=1.0.0".getBytes(StandardCharsets.UTF_8));//pom.xmljos.putNextEntry(newJarEntry("/META-INF/maven/com.myspace/test/pom.xml"));jos.write("4.0.0com.myspacetest1.0.0kjar测试.getBytes(StandardCharsets.UTF_8));//该死的jos.putNextEntry(newJarEntry("/config/rules/test.dmn"));jos.write(dmn.getBytes(StandardCharsets.UTF_8));jos.finish();ByteArrayInputStreaminputStream=newByteArrayInputStream(outputStream.toByteArray());//初始化KieServicesKieServiceskieServices=KieServices.Factory.get();//获取KieServices的数据源KieRepositoryrepository=kieServices.getRepository();//添加基于生成的二进制流的KeiModuleKieModule到数据源kieModule=repository.addKieModule(kieServices.getResources().newInputStreamResource(inputStream));如上,在没有maven环境DMN规则的情况下,我们仍然可以让KieServices得到我们需要的,但这还不够。创建KieContainer时,内部会生成一个KieProject实例。在实例化过程中,KieProject会默认生成一个基于maven的MavenClassLoaderResolver来查找jar,在缺少maven环境的情况下,生成MavenClassLoaderResolver会报错。为此,我们需要替换MavenClassLoaderResolver。通过分析Drools源码可以发现,在源码中,有时会使用ProjectClassLoader.findParentClassLoader()来获取基于当前运行环境的ClassLoader。因此,在创建KieContainer时需要使用ProjectClassLoader.findParentClassLoader()生成的ClassLoader替换默认的MavenClassLoaderResolver://使用另一个接收ClassLoader的接口生成KieContainerKieContainerkieContainer=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.htmlDrools官方15分钟简单教学:https://learn-dmn-in-15-minutes.com/learn/introductionKogito在线编辑网页:https://sandbox.kie.org/#/Kie官网:https://www.kie.org/OMG官网DMN页面:https://www.omg.org/dmn/作者简介向颖是一名高级java工程师。长期服务于转转图书项目。