企業(yè)開發(fā)人員按照管理復(fù)雜工作流、業(yè)務(wù)規(guī)則和業(yè)務(wù)智能來(lái)分派任務(wù),這樣可以快速實(shí)現(xiàn)企業(yè)平臺(tái)的價(jià)值,該平臺(tái)集成了工作流引擎、企業(yè)服務(wù)總線 (ESB) 和規(guī)則引擎。迄今為止,這個(gè)出色的平臺(tái)已經(jīng)被 IBM WebSphere? Process Server/WebSphere Enterprise Service Bus(參見 參考資料)和 Oracle SOA Suite 之類的商用產(chǎn)品填滿了。來(lái)自 Boss Community 的 Drools 5 是一種開源替代方案,它通過(guò)一組統(tǒng)一的 API 和一個(gè)共享的、有狀態(tài)的知識(shí)會(huì)話來(lái)無(wú)縫集成 jBPM 工作流引擎和規(guī)則引擎。 Drools 5 的 Business Logic 集成平臺(tái)主要包括 Drools Expert 和 Drools Fusion,這兩項(xiàng)共同組成了平臺(tái)的規(guī)則引擎和用于復(fù)雜事件處理/時(shí)態(tài)推理的基礎(chǔ)架構(gòu)。本文的樣例應(yīng)用程序是根據(jù)這些核心特性構(gòu)建的。請(qǐng)參閱 參考資料,了解有關(guān) Drools 5 中其他可用程序包的更多信息。 Drools 5 中的 POJO傳 統(tǒng)的 Java 對(duì)象 (POJO) 是在 Spring 框架中首次以引人注目的方式實(shí)現(xiàn)的。POJO 以及依賴注入 (DI) 和面向方面的編程 (AOP) 共同標(biāo)志著向簡(jiǎn)單性的回歸,這種簡(jiǎn)單性有效地促進(jìn) Spring 成為開發(fā) Web 應(yīng)用程序的一種行業(yè)標(biāo)準(zhǔn)。POJO 的采用已經(jīng)從 Spring 流向 EJB 3.0 和 JPA,然后再流向 XML-to-Java 綁定技術(shù)(比如 JAXB 和 XStream)。最近,POJO 已通過(guò) Hibernate Search 集成到了全文搜索引擎 Lucene 中(參閱 參考資料)。 如今,由于這些增加的改進(jìn),應(yīng)用程序的 POJO 數(shù)據(jù)模型可以在多個(gè)層上進(jìn)行傳播,并直接通過(guò) Web 頁(yè)面或 SOAP/REST Web 服務(wù)端點(diǎn)進(jìn)行公開。作為一種編程模型,POJO 既經(jīng)濟(jì)高效又屬于非侵入性的,這為開發(fā)人員在簡(jiǎn)化企業(yè)架構(gòu)時(shí)節(jié)約了不少時(shí)間。 現(xiàn)在,Drools 5 通過(guò)允許直接將 POJO 作為事實(shí) (fact) 直接插入知識(shí)會(huì)話(knowledge session)中,或是插入一個(gè)稱為 “工作內(nèi)存” 的規(guī)則引擎中,將 POJO 編程簡(jiǎn)單性應(yīng)用于下一個(gè)級(jí)別。本文介紹了一種既經(jīng)濟(jì)高效又屬于非侵入性的方法,這種方法將 JPA 實(shí)體作為 Drools 工作內(nèi)存中的事實(shí)來(lái)進(jìn)行操作。持續(xù)的實(shí)時(shí)數(shù)據(jù)分析從來(lái)不會(huì)這么簡(jiǎn)單。 Drools 編程挑戰(zhàn)許多醫(yī)療服務(wù)提供商使用案例管理系統(tǒng)作為跟蹤醫(yī)療記錄(比如護(hù)理、處方和評(píng)估)的一種經(jīng)濟(jì)高效的方法。我們的示例程序(基于這樣一種系統(tǒng))具有以下流程和需求:
為 該用例選擇一個(gè)業(yè)務(wù)流程管理 (BPM) 工作流和規(guī)則引擎是有一定道理的:系統(tǒng)使用數(shù)據(jù)剖析/分析規(guī)則(已在上述列表中用斜體字標(biāo)出),將每個(gè)案例用作在 jBPM 中長(zhǎng)期運(yùn)行的一個(gè)流程/工作流,而且我們可以使用一個(gè) Drools Planner 來(lái)滿足自動(dòng)安排的需求。出于本文的目的,我們將只關(guān)注程序的業(yè)務(wù)規(guī)則。我們還要介紹的是系統(tǒng)需求,在滿足規(guī)則條件時(shí)立即實(shí)時(shí)生成提醒和通知。因此這是一個(gè) 持續(xù)的實(shí)時(shí)數(shù)據(jù)分析用例。 清單 1 顯示了在我們的系統(tǒng)中聲明的三個(gè)實(shí)體類: 清單 1. 實(shí)體類@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; //... }
從應(yīng)用程序的角度來(lái)看,我們可以從系統(tǒng)的任何地方、在不同的屏幕上、在不同的工作流中修改這三種類型的實(shí)體。我們甚至可以使用 Spring Batch 這樣的工具來(lái)批量更新實(shí)體。然而,出于本例的考慮,讓我們假設(shè)將只通過(guò) JPA 持久上下文來(lái)更新實(shí)體。 注意,樣例應(yīng)用程序是一個(gè) Spring-Drools 集成,它使用 Maven 來(lái)完成構(gòu)建。本文稍后將考慮一些配置細(xì)節(jié),但是您可以隨時(shí) 下載源 zip?,F(xiàn)在,讓我們考慮一些使用 Drools 5 的概念特性。 事實(shí)和 FactHandle規(guī)則引擎的一般概念是:事實(shí) (fact) 是規(guī)則所依賴的數(shù)據(jù)對(duì)象。在 Drools 中,事實(shí)是從應(yīng)用程序獲得且斷言為引擎的工作內(nèi)存的任意 Java bean?;蛘哒f(shuō),就像在 JBoss Drools 參考手冊(cè) 中撰寫的那樣: 規(guī) 則引擎根本沒有 “克隆” 事實(shí),它是一天結(jié)束時(shí)的所有引用/指針 (pointer)。事實(shí)是您的應(yīng)用程序數(shù)據(jù)。沒有 getter 和 setter 的 Strings 和其他類不是有效的 Fact,不能和 Field Constraints 一起使用,F(xiàn)ield Constraints 依靠 getter 和 setter 的 JavaBean 標(biāo)準(zhǔn)與對(duì)象進(jìn)行交互。 除非您在規(guī)則之上已經(jīng)指定了關(guān)鍵字 出于實(shí)際維護(hù)的目的,有三種方法來(lái)安全更新 Drools 工作內(nèi)存中的事實(shí):
作為安靜的觀察者,我們的規(guī)則不會(huì)更新 Drools 工作內(nèi)存中的任何 JPA 實(shí)體事實(shí);相反,它們會(huì)將邏輯事實(shí)生成為推理結(jié)果。(參見下列的 清單 6。)但是,更新規(guī)則中的 JPA 實(shí)體時(shí)需要特別注意,因?yàn)楦碌膶?shí)體可能處于分離狀態(tài),或者沒有事務(wù)或只讀事務(wù)與當(dāng)前線程有關(guān)聯(lián)。因此,對(duì)實(shí)體所做的更改將不會(huì)保存到數(shù)據(jù)庫(kù)中。 盡管事實(shí)對(duì)象是因?yàn)橐枚粋鬟f,Drools(與 JPA/Hibernate 不同)不能跟蹤超出規(guī)則之外的事實(shí)更改。您可以通過(guò)使用 您可以通過(guò)實(shí)現(xiàn) 使用 JPA 實(shí)體作為事實(shí)您可以通過(guò) POJO 事實(shí)將 JPA 實(shí)體作為域數(shù)據(jù)對(duì)象插入到 Drools 的工作內(nèi)存中。這樣做可以讓您避免對(duì) Value Object/DTO 層以及 JPA 實(shí)體和 DTO 之間的相應(yīng)轉(zhuǎn)換層進(jìn)行數(shù)據(jù)建模。 將 實(shí)體用作事實(shí)會(huì)簡(jiǎn)化應(yīng)用程序代碼,您必須額外注意 “實(shí)體-生命周期” 階段。實(shí)體事實(shí)應(yīng)當(dāng)保存為受管(持久)狀態(tài)或分離狀態(tài)。永遠(yuǎn)不要將臨時(shí)的實(shí)體插入到 Drools 工作內(nèi)存中,因?yàn)樗鼈冞€未保存到數(shù)據(jù)庫(kù)中。同樣,應(yīng)當(dāng)從工作內(nèi)存中收回已刪除的實(shí)體。否則應(yīng)用程序數(shù)據(jù)庫(kù)和規(guī)則引擎的工作內(nèi)存會(huì)不同步。 因此,這會(huì)帶來(lái)一些嚴(yán)重的問(wèn)題:我們?nèi)绾尾拍苡行ㄖ?guī)則引擎有關(guān)通過(guò) 命令式(Imperative)編程與 AOP 的比較如果想通過(guò)命令式編程的方式來(lái)應(yīng)對(duì)這個(gè)挑戰(zhàn),我們需要結(jié)束在緊鄰相應(yīng) JPA API 方法的知識(shí)會(huì)話上調(diào)用 JPA 在 “實(shí)體-生命周期” 回調(diào)方法中,我們?yōu)榻o定的實(shí)體實(shí)例查找一個(gè) 清單 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 中的 初始化工作內(nèi)存在啟動(dòng)應(yīng)用程序后,三種實(shí)體類型的所有現(xiàn)有記錄都將從數(shù)據(jù)庫(kù)預(yù)加載到用于規(guī)則執(zhí)行的工作內(nèi)存中,如 清單 3 所示。從那時(shí)起,會(huì)向工作內(nèi)存通知通過(guò)兩個(gè) 清單 3. 初始化工作內(nèi)存并運(yùn)行 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; } } } 有關(guān) fireAllRules() 的注意事項(xiàng)請(qǐng)注意,在 清單 3 中,我們擁有在各個(gè) 我可以選擇在應(yīng)用程序的 Spring XML 配置文件(如下所示)中觸發(fā)規(guī)則,甚至是啟動(dòng)流程。然而,我在嘗試配置 Spring-Drools 集成現(xiàn)在,讓我們花一些時(shí)間來(lái)看看 Spring-Drools 集成的一些配置細(xì)節(jié)。清單 4 是應(yīng)用程序的 Maven pom.xml 的一個(gè)代碼段,包括用于 Drools 內(nèi)核、Drools 編譯器和 Drools Spring 集成包的依賴關(guān)系: 清單 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 中,我將一個(gè)全局有狀態(tài)知識(shí)會(huì)話配置為一個(gè)單態(tài)的 Spring bean。(一個(gè)無(wú)狀態(tài)知識(shí)會(huì)話不會(huì)充當(dāng)一個(gè)持續(xù)時(shí)間很長(zhǎng)的會(huì)話,因?yàn)樗诘{(diào)用期間沒有保持其狀態(tài)。)清單 5 中需要注意的一個(gè)重要設(shè)置是 在 JPA/Hibernate 中,托管實(shí)體將與身份(identity) 進(jìn)行比較,而分離的實(shí)體將與等同性(equality) 進(jìn)行比較。插入到有狀態(tài)實(shí)體會(huì)話中的實(shí)體快速?gòu)?JPA 角度分離。因?yàn)榕c單態(tài)的有狀態(tài)知識(shí)會(huì)話的生命期相比,一個(gè)事務(wù)范圍的持續(xù)上下文,甚至是一個(gè) “擴(kuò)展的” 或 “流范圍的” 持續(xù)上下文(參見 參考資料)是臨時(shí)的。每次通過(guò)不同的持續(xù)上下文對(duì)象取得的同一個(gè)實(shí)體是不同的 Java 對(duì)象。默認(rèn)情況下,Drools 使用的是身份比較。因此,當(dāng)通過(guò) 清單 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" /> 看看應(yīng)用程序源代碼,了解更完整的配置細(xì)節(jié)。 Drools 規(guī)則清單 6 定義了兩個(gè)復(fù)雜的事件處理 (CEP) 規(guī)則。除了類似 JPA 的兩個(gè)事實(shí)類型之外, 清單 6 中的 Case Supervision 規(guī)則的條件可用來(lái)測(cè)試在過(guò)去的 30 天內(nèi)案例上是否已經(jīng)存在案例監(jiān)督。如果沒有,規(guī)則的結(jié)果/措施部分會(huì)生成一個(gè) 清單 6. 案例監(jiān)督規(guī)則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 請(qǐng)注意,清單 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); } } 事實(shí)與事件的比較事件是使用 Drools 對(duì) Sliding Windows 協(xié)議的支持使得事件對(duì)時(shí)態(tài)推理特別有用。滑動(dòng)窗口 是為感興趣的事件制定作用域的一種方式,就好像它們屬于一個(gè)不斷移動(dòng)的窗口一樣。兩種最常見的滑動(dòng)窗口實(shí)現(xiàn)是基于時(shí)間的窗口和基于長(zhǎng)度的窗口。 在 清單 6 中所示的樣例規(guī)則中, 使用已聲明的類型在 清單 6 中需要注意的其他一些事項(xiàng)包括: 這種特殊的事件類型根本不聲明任何顯式屬性,除了一個(gè)隱式的 要想在每天的一開始重設(shè) 何時(shí)使用事實(shí)與事件的比較了解事實(shí)和事件之間的區(qū)別有助于我們輕松決定何時(shí)使用每種類型:
Drools 查詢下一個(gè)步驟是提取規(guī)則執(zhí)行結(jié)果,這通過(guò)查詢工作內(nèi)存中的事實(shí)來(lái)完成。(一種替代方法是通過(guò)調(diào)用規(guī)則語(yǔ)法右手邊的 可以說(shuō)提醒 是在早上由一個(gè)規(guī)則引擎在特定的案例上生成的。隨后,我們?cè)?Java 代碼中執(zhí)行查詢 “ 現(xiàn)場(chǎng)查詢 更是錦上添花。讓一個(gè)現(xiàn)場(chǎng)查詢處于打開狀態(tài),這會(huì)創(chuàng)建一個(gè)查詢結(jié)果視圖,并發(fā)布給定視圖內(nèi)容的更改事件。這意味著現(xiàn)場(chǎng)查詢恰好需要運(yùn)行一次,由此產(chǎn)生的結(jié)果視圖會(huì)使用由規(guī)則引擎發(fā)布的正在進(jìn)行的更改來(lái)實(shí)現(xiàn)自動(dòng)更新。 到目前為止,您可能已經(jīng)發(fā)現(xiàn),只需一點(diǎn)點(diǎn)有關(guān) Drools、JPA 和 Spring 的背景知識(shí),就可以輕松實(shí)現(xiàn)一個(gè)持續(xù)的實(shí)時(shí)數(shù)據(jù)分析應(yīng)用程序。我們將通過(guò)一些將改進(jìn)我們的案例管理解決方案的高級(jí)編程步驟來(lái)結(jié)束本文。 高級(jí) Drools 編程管理關(guān)系
同樣,系統(tǒng)不會(huì)通知 JPA 中的 為了根據(jù)更新的事實(shí)與這些關(guān)系建立連接,我們可以構(gòu)建遞歸邏輯,獲取每個(gè)嵌套關(guān)系的 規(guī)則評(píng)估期間的實(shí)體懶惰式加載除非我們已經(jīng)指定一個(gè)知識(shí)庫(kù)分區(qū)(也就是說(shuō),可以執(zhí)行并行處理),否則不會(huì)在調(diào)用 啟用事務(wù)默認(rèn)情況下,Drools 不支持事務(wù),因?yàn)樗诠ぷ鲀?nèi)存中不保存任何歷史快照。這對(duì)我們的 啟用事務(wù)通過(guò)確保工作內(nèi)存中的數(shù)據(jù)庫(kù)和應(yīng)用程序數(shù)據(jù)庫(kù)始終同步,且規(guī)則推理結(jié)果始終準(zhǔn)確,使我們的案例管理系統(tǒng)具有防彈功能。在 Drools 中,適當(dāng)應(yīng)用 JPA 和 JTA 實(shí)現(xiàn)以及類路徑中的一個(gè) “ 當(dāng)
我們通過(guò)注釋或 XML
在應(yīng)用程序中指定事務(wù)邊界時(shí),應(yīng)用程序啟動(dòng)的事務(wù)會(huì)傳播到規(guī)則引擎。無(wú)論什么時(shí)候發(fā)生事務(wù)回滾,有狀態(tài)知識(shí)會(huì)話都會(huì)恢復(fù)到數(shù)據(jù)庫(kù)中保存的以前的狀態(tài)。這維
護(hù)了應(yīng)用程序數(shù)據(jù)庫(kù)和 Drools 數(shù)據(jù)庫(kù)之間的一致性和集成。當(dāng)同時(shí)從多個(gè) JTA 事務(wù)中進(jìn)行訪問(wèn)時(shí),內(nèi)存中的單個(gè)有狀態(tài)知識(shí)會(huì)話應(yīng)當(dāng)像 集群如果應(yīng)用程序要在集群環(huán)境下運(yùn)行,前面描述的方法很快就會(huì)失敗。每個(gè)嵌入式規(guī)則引擎的實(shí)例都會(huì)接收同一個(gè)節(jié)點(diǎn)上發(fā)生的實(shí)體事件,這會(huì)導(dǎo)致不同節(jié)點(diǎn)上的工作內(nèi)存不同步。我們可以使用一個(gè)通用的遠(yuǎn)程 Drools 服務(wù)器(參閱 參考資料)來(lái)解決這個(gè)問(wèn)題。不同節(jié)點(diǎn)上的實(shí)體實(shí)例會(huì)通過(guò) REST/SOAP Web 服務(wù)通信向集中式 Drools 服務(wù)器發(fā)布其所有的事件。隨后應(yīng)用程序可以從 Drools 服務(wù)器訂閱推理結(jié)果。請(qǐng)注意,Drools 服務(wù)器中 SOAP 的 Apache CXF 實(shí)現(xiàn)目前不支持 結(jié)束語(yǔ)在本文中,您有機(jī)會(huì)匯總一些您已經(jīng)了解的有關(guān)在 Spring 和 JPA 中進(jìn)行 POJO 編程的知識(shí),同時(shí)本文還匯總了一些在 Drools 5 中可用的新特性。我已經(jīng)演示了如何巧妙使用 下載
參考資料學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
討論
|
|