企业开发人员按照管理复杂工作流、业务规则和业务智能来分派任务,这样可以快速实现企业平台的价值,该平台集成了工作流引擎、企业服务总线 (ESB) 和规则引擎。迄今为止,这个出色的平台已经被 IBM WebSphere? Process Server/WebSphere Enterprise Service Bus(参见 参考资料)和 Oracle SOA Suite 之类的商用产品填满了。来自 Boss Community 的 Drools 5 是一种开源替代方案,它通过一组统一的 API 和一个共享的、有状态的知识会话来无缝集成 jBPM 工作流引擎和规则引擎。 Drools 5 的 Business Logic 集成平台主要包括 Drools Expert 和 Drools Fusion,这两项共同组成了平台的规则引擎和用于复杂事件处理/时态推理的基础架构。本文的样例应用程序是根据这些核心特性构建的。请参阅 参考资料,了解有关 Drools 5 中其他可用程序包的更多信息。 Drools 5 中的 POJO传 统的 Java 对象 (POJO) 是在 Spring 框架中首次以引人注目的方式实现的。POJO 以及依赖注入 (DI) 和面向方面的编程 (AOP) 共同标志着向简单性的回归,这种简单性有效地促进 Spring 成为开发 Web 应用程序的一种行业标准。POJO 的采用已经从 Spring 流向 EJB 3.0 和 JPA,然后再流向 XML-to-Java 绑定技术(比如 JAXB 和 XStream)。最近,POJO 已通过 Hibernate Search 集成到了全文搜索引擎 Lucene 中(参阅 参考资料)。 如今,由于这些增加的改进,应用程序的 POJO 数据模型可以在多个层上进行传播,并直接通过 Web 页面或 SOAP/REST Web 服务端点进行公开。作为一种编程模型,POJO 既经济高效又属于非侵入性的,这为开发人员在简化企业架构时节约了不少时间。 现在,Drools 5 通过允许直接将 POJO 作为事实 (fact) 直接插入知识会话(knowledge session)中,或是插入一个称为 “工作内存” 的规则引擎中,将 POJO 编程简单性应用于下一个级别。本文介绍了一种既经济高效又属于非侵入性的方法,这种方法将 JPA 实体作为 Drools 工作内存中的事实来进行操作。持续的实时数据分析从来不会这么简单。 Drools 编程挑战许多医疗服务提供商使用案例管理系统作为跟踪医疗记录(比如护理、处方和评估)的一种经济高效的方法。我们的示例程序(基于这样一种系统)具有以下流程和需求:
为 该用例选择一个业务流程管理 (BPM) 工作流和规则引擎是有一定道理的:系统使用数据剖析/分析规则(已在上述列表中用斜体字标出),将每个案例用作在 jBPM 中长期运行的一个流程/工作流,而且我们可以使用一个 Drools Planner 来满足自动安排的需求。出于本文的目的,我们将只关注程序的业务规则。我们还要介绍的是系统需求,在满足规则条件时立即实时生成提醒和通知。因此这是一个 持续的实时数据分析用例。 清单 1 显示了在我们的系统中声明的三个实体类: 清单 1. 实体类@Entity @EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class}) public class MemberCase implements Serializable { private Long id; // pk private Date startDtm; private Date endDtm; private Member member; // not null (memberId) private List<CaseSupervision> caseSupervisions = new ArrayList<CaseSupervision>(); //... } @Entity @EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class}) public class Clinician implements Serializable { private Long id; // pk private Boolean active; private List<CaseSupervision> caseSupervisions = new ArrayList<CaseSupervision>(); //... } @Entity @EntityListeners({SupervisionStreamWorkingMemoryPartitionEntityListener.class}) public class CaseSupervision implements Serializable { private Long id; // pk private Date entryDtm; private MemberCase memberCase; private Clinician clinician; //... }
从应用程序的角度来看,我们可以从系统的任何地方、在不同的屏幕上、在不同的工作流中修改这三种类型的实体。我们甚至可以使用 Spring Batch 这样的工具来批量更新实体。然而,出于本例的考虑,让我们假设将只通过 JPA 持久上下文来更新实体。 注意,样例应用程序是一个 Spring-Drools 集成,它使用 Maven 来完成构建。本文稍后将考虑一些配置细节,但是您可以随时 下载源 zip。现在,让我们考虑一些使用 Drools 5 的概念特性。 事实和 FactHandle规则引擎的一般概念是:事实 (fact) 是规则所依赖的数据对象。在 Drools 中,事实是从应用程序获得且断言为引擎的工作内存的任意 Java bean。或者说,就像在 JBoss Drools 参考手册 中撰写的那样: 规 则引擎根本没有 “克隆” 事实,它是一天结束时的所有引用/指针 (pointer)。事实是您的应用程序数据。没有 getter 和 setter 的 Strings 和其他类不是有效的 Fact,不能和 Field Constraints 一起使用,Field Constraints 依靠 getter 和 setter 的 JavaBean 标准与对象进行交互。 除非您在规则之上已经指定了关键字 出于实际维护的目的,有三种方法来安全更新 Drools 工作内存中的事实:
作为安静的观察者,我们的规则不会更新 Drools 工作内存中的任何 JPA 实体事实;相反,它们会将逻辑事实生成为推理结果。(参见下列的 清单 6。)但是,更新规则中的 JPA 实体时需要特别注意,因为更新的实体可能处于分离状态,或者没有事务或只读事务与当前线程有关联。因此,对实体所做的更改将不会保存到数据库中。 尽管事实对象是因为引用而被传递,Drools(与 JPA/Hibernate 不同)不能跟踪超出规则之外的事实更改。您可以通过使用 您可以通过实现 使用 JPA 实体作为事实您可以通过 POJO 事实将 JPA 实体作为域数据对象插入到 Drools 的工作内存中。这样做可以让您避免对 Value Object/DTO 层以及 JPA 实体和 DTO 之间的相应转换层进行数据建模。 将 实体用作事实会简化应用程序代码,您必须额外注意 “实体-生命周期” 阶段。实体事实应当保存为受管(持久)状态或分离状态。永远不要将临时的实体插入到 Drools 工作内存中,因为它们还未保存到数据库中。同样,应当从工作内存中收回已删除的实体。否则应用程序数据库和规则引擎的工作内存会不同步。 因此,这会带来一些严重的问题:我们如何才能有效通知规则引擎有关通过 命令式(Imperative)编程与 AOP 的比较如果想通过命令式编程的方式来应对这个挑战,我们需要结束在紧邻相应 JPA API 方法的知识会话上调用 JPA 在 “实体-生命周期” 回调方法中,我们为给定的实体实例查找一个 清单 2. EntityListeners@Configurable public class DefaultWorkingMemoryPartitionEntityListener { @Value("#{ksession}") //unable to make @Configurable with compile time weaving work here private StatefulKnowledgeSession ksession; @PostPersist @PostUpdate public void updateFact(Object entity) { FactHandle factHandle = getKsession().getFactHandle(entity); if(factHandle == null) getKsession().insert(entity); else getKsession().update(factHandle, entity); } @PostRemove public void retractFact(Object entity) { FactHandle factHandle = getKsession().getFactHandle(entity); if(factHandle != null) getKsession().retract(factHandle); } public StatefulKnowledgeSession getKsession() { if(ksession != null) { return ksession; } else { // a workaround for @Configurable setKsession(ApplicationContextProvider.getApplicationContext() .getBean("ksession", StatefulKnowledgeSession.class)); return ksession; } } //... } @Configurable public class SupervisionStreamWorkingMemoryPartitionEntityListener { @Value("#{ksession}") private StatefulKnowledgeSession ksession; @PostPersist // CaseSupervision is an immutable event, // thus we don’t provide @PostUpdate and @PostRemove implementations. public void insertFact(Object entity) { WorkingMemoryEntryPoint entryPoint = getKsession() .getWorkingMemoryEntryPoint("SupervisionStream"); entryPoint.insert(entity); } //... } 就像 AOP 一样,清单 2 中的 初始化工作内存在启动应用程序后,三种实体类型的所有现有记录都将从数据库预加载到用于规则执行的工作内存中,如 清单 3 所示。从那时起,会向工作内存通知通过两个 清单 3. 初始化工作内存并运行 Drools 查询@Service("droolsService") @Lazy(false) @Transactional public class DroolsServiceImpl { @Value("#{droolsServiceUtil}") private DroolsServiceUtil droolsServiceUtil; @PostConstruct public void launchRules() { droolsServiceUtil.initializeKnowledgeSession(); droolsServiceUtil.fireRulesUtilHalt(); } public Collection<TransientReminder> findCaseReminders() { return droolsServiceUtil.droolsQuery("CaseReminderQuery", "caseReminder", TransientReminder.class, null); } public Collection<TransientReminder> findClinicianReminders() { return droolsServiceUtil.droolsQuery("ClinicianReminderQuery", "clinicianReminder", TransientReminder.class, null); } } @Service public class DroolsServiceUtil { @Value("#{ksession}") private StatefulKnowledgeSession ksession; @Async public void fireRulesUtilHalt() { try{ getKsession().fireUntilHalt(); }catch(ConsequenceException e) { throw e; } } public void initializeKnowledgeSession() { getKsession().setGlobal("droolsServiceUtil", this); syncFactsWithDatabase(); } @Transactional //a transaction-scoped persistence context public void syncFactsWithDatabase() { synchronized(ksession) { // Reset all the facts in the working memory Collection<FactHandle> factHandles = getKsession().getFactHandles( new ObjectFilter(){public boolean accept(Object object) { if(object instanceof MemberCase) return true; return false; } }); for(FactHandle factHandle : factHandles) { getKsession().retract(factHandle); } factHandles = getKsession().getFactHandles( new ObjectFilter(){public boolean accept(Object object) { if(object instanceof Clinician) return true; return false; } }); for(FactHandle factHandle : factHandles) { getKsession().retract(factHandle); } WorkingMemoryEntryPoint entryPoint = getKsession() .getWorkingMemoryEntryPoint("SupervisionStream"); factHandles = entryPoint.getFactHandles(); for(FactHandle factHandle : factHandles) { entryPoint.retract(factHandle); } List<Command> commands = new ArrayList<Command>(); commands.add(CommandFactory.newInsertElements(getMemberCaseService().findAll())); getKsession().execute(CommandFactory.newBatchExecution(commands)); commands = new ArrayList<Command>(); commands.add(CommandFactory.newInsertElements(getClinicianService().findAll())); getKsession().execute(CommandFactory.newBatchExecution(commands)); for(CaseSupervision caseSupervision : getCaseSupervisionService().findAll()) { entryPoint.insert(caseSupervision); } } } public <T> Collection<T> droolsQuery(String query, String variable, Class<T> c, Object... args) { synchronized(ksession) { Collection<T> results = new ArrayList<T>(); QueryResults qResults = getKsession().getQueryResults(query, args); for(QueryResultsRow qrr : qResults) { T result = (T) qrr.get("$"+variable); results.add(result); } return results; } } } 有关 fireAllRules() 的注意事项请注意,在 清单 3 中,我们拥有在各个 我可以选择在应用程序的 Spring XML 配置文件(如下所示)中触发规则,甚至是启动流程。然而,我在尝试配置 Spring-Drools 集成现在,让我们花一些时间来看看 Spring-Drools 集成的一些配置细节。清单 4 是应用程序的 Maven pom.xml 的一个代码段,包括用于 Drools 内核、Drools 编译器和 Drools Spring 集成包的依赖关系: 清单 4. 部分 Maven pom.xml<dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>5.4.0.Final</version> <type>jar</type> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>5.4.0.Final</version> <type>jar</type> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-spring</artifactId> <version>5.4.0.Final</version> <type>jar</type> <exclusions> <!-- The dependency pom includes spring and hibernate dependencies by mistake. --> </exclusions> </dependency> 身份与等同性的比较在 清单 5 中,我将一个全局有状态知识会话配置为一个单态的 Spring bean。(一个无状态知识会话不会充当一个持续时间很长的会话,因为它在迭代调用期间没有保持其状态。)清单 5 中需要注意的一个重要设置是 在 JPA/Hibernate 中,托管实体将与身份(identity) 进行比较,而分离的实体将与等同性(equality) 进行比较。插入到有状态实体会话中的实体快速从 JPA 角度分离。因为与单态的有状态知识会话的生命期相比,一个事务范围的持续上下文,甚至是一个 “扩展的” 或 “流范围的” 持续上下文(参见 参考资料)是临时的。每次通过不同的持续上下文对象取得的同一个实体是不同的 Java 对象。默认情况下,Drools 使用的是身份比较。因此,当通过 清单 5. 部分 Spring applicationContext.xml<drools:kbase id="kbase"> <drools:resources> <drools:resource type="DRL" source="classpath:drools/rules.drl" /> </drools:resources> <drools:configuration> <drools:mbeans enabled="true" /> <drools:event-processing-mode mode="STREAM" /> <drools:assert-behavior mode="EQUALITY" /> </drools:configuration> </drools:kbase> <drools:ksession id="ksession" type="stateful" name="ksession" kbase="kbase" /> 看看应用程序源代码,了解更完整的配置细节。 Drools 规则清单 6 定义了两个复杂的事件处理 (CEP) 规则。除了类似 JPA 的两个事实类型之外, 清单 6 中的 Case Supervision 规则的条件可用来测试在过去的 30 天内案例上是否已经存在案例监督。如果没有,规则的结果/措施部分会生成一个 清单 6. 案例监督规则package ibm.developerworks.article.drools; import ibm.developerworks.article.drools.service.* import ibm.developerworks.article.drools.domain.* global DroolsServiceUtil droolsServiceUtil; declare Today @role(event) @expires(24h) end declare CaseSupervision @role(event) @timestamp(entryDtm) end rule "Set Today" timer (cron: 0 0 0 * * ?) salience 99999 // optional no-loop when then insert(new Today()); end rule "Case Supervision" dialect "mvel" when $today : Today() $memberCase : MemberCase(endDtm == null, startDtm before[30d] $today) not CaseSupervision(memberCase == $ memberCase) over window:time(30d) from entry-point SupervisionStream then insertLogical(new TransientReminder($memberCase, (Clinician)null, "CaseReminder", "No supervision on the case in last 30 days.")); end query "CaseReminderQuery" $caseReminder : TransientReminder(reminderTypeCd == "CaseReminder") end rule "Clinician Supervision" dialect "mvel" when $clinician : Clinician() not CaseSupervision(clinician == $clinician) over window:time(7d) from entry-point SupervisionStream then insertLogical(new TransientReminder((MemberCase)null, $clinician, "ClinicianReminder", "Clinician completed no evaluation in last 7 days.")); end query "ClinicianReminderQuery" $clinicianReminder : TransientReminder(reminderTypeCd == "ClinicianReminder") end 请注意,清单 7 中所示的 清单 7. TransientReminderpublic class TransientReminder implements Comparable, Serializable { private MemberCase memberCase; private Clinician clinician; private String reminderTypeCd; private String description; public String toString() { return ReflectionToStringBuilder.toString(this); } public boolean equals(Object pObject) { return EqualsBuilder.reflectionEquals(this, pObject); } public int compareTo(Object pObject) { return CompareToBuilder.reflectionCompare(this, pObject); } public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } } 事实与事件的比较事件是使用 Drools 对 Sliding Windows 协议的支持使得事件对时态推理特别有用。滑动窗口 是为感兴趣的事件制定作用域的一种方式,就好像它们属于一个不断移动的窗口一样。两种最常见的滑动窗口实现是基于时间的窗口和基于长度的窗口。 在 清单 6 中所示的样例规则中, 使用已声明的类型在 清单 6 中需要注意的其他一些事项包括: 这种特殊的事件类型根本不声明任何显式属性,除了一个隐式的 要想在每天的一开始重设 何时使用事实与事件的比较了解事实和事件之间的区别有助于我们轻松决定何时使用每种类型:
Drools 查询下一个步骤是提取规则执行结果,这通过查询工作内存中的事实来完成。(一种替代方法是通过调用规则语法右手边的 可以说提醒 是在早上由一个规则引擎在特定的案例上生成的。随后,我们在 Java 代码中执行查询 “ 现场查询 更是锦上添花。让一个现场查询处于打开状态,这会创建一个查询结果视图,并发布给定视图内容的更改事件。这意味着现场查询恰好需要运行一次,由此产生的结果视图会使用由规则引擎发布的正在进行的更改来实现自动更新。 到目前为止,您可能已经发现,只需一点点有关 Drools、JPA 和 Spring 的背景知识,就可以轻松实现一个持续的实时数据分析应用程序。我们将通过一些将改进我们的案例管理解决方案的高级编程步骤来结束本文。 高级 Drools 编程管理关系
同样,系统不会通知 JPA 中的 为了根据更新的事实与这些关系建立连接,我们可以构建递归逻辑,获取每个嵌套关系的 规则评估期间的实体懒惰式加载除非我们已经指定一个知识库分区(也就是说,可以执行并行处理),否则不会在调用 启用事务默认情况下,Drools 不支持事务,因为它在工作内存中不保存任何历史快照。这对我们的 启用事务通过确保工作内存中的数据库和应用程序数据库始终同步,且规则推理结果始终准确,使我们的案例管理系统具有防弹功能。在 Drools 中,适当应用 JPA 和 JTA 实现以及类路径中的一个 “ 当
我们通过注释或 XML
在应用程序中指定事务边界时,应用程序启动的事务会传播到规则引擎。无论什么时候发生事务回滚,有状态知识会话都会恢复到数据库中保存的以前的状态。这维
护了应用程序数据库和 Drools 数据库之间的一致性和集成。当同时从多个 JTA 事务中进行访问时,内存中的单个有状态知识会话应当像 集群如果应用程序要在集群环境下运行,前面描述的方法很快就会失败。每个嵌入式规则引擎的实例都会接收同一个节点上发生的实体事件,这会导致不同节点上的工作内存不同步。我们可以使用一个通用的远程 Drools 服务器(参阅 参考资料)来解决这个问题。不同节点上的实体实例会通过 REST/SOAP Web 服务通信向集中式 Drools 服务器发布其所有的事件。随后应用程序可以从 Drools 服务器订阅推理结果。请注意,Drools 服务器中 SOAP 的 Apache CXF 实现目前不支持 结束语在本文中,您有机会汇总一些您已经了解的有关在 Spring 和 JPA 中进行 POJO 编程的知识,同时本文还汇总了一些在 Drools 5 中可用的新特性。我已经演示了如何巧妙使用 下载
参考资料学习
获得产品和技术
讨论
|
|
来自: LibraryPKU > 《技术动态》