第4章 規(guī)則流
一個規(guī)則流是一個流程,使用一個流程圖描述一系列需要執(zhí)行步驟的順序。一個流程由使用線路互相連結(jié)的節(jié)點的一個集合構(gòu)成。每個節(jié)點表示在整個流程中的一個步驟,同時線路指定了如何從一個節(jié)點轉(zhuǎn)換到另一個節(jié)點。一個全面的預(yù)定義節(jié)點類型已經(jīng)被定義。本章描述如何定義這種流程,以及如何在你的應(yīng)用程序中使用它們。
可以使用下面三種方法之一創(chuàng)建流程:
4.1.1 圖形規(guī)則流編輯器
圖形規(guī)則流編輯器是一個編輯器,允許你在圖布上拖放不同的節(jié)點創(chuàng)建一個流程,并且允許編輯這些節(jié)點的屬性。這個圖形編輯器是Eclipse的Drools插件的一部分。一旦你創(chuàng)建了一個Drools項目(如果你不知道如何做,請查看IDE章節(jié)),你就可以開始添加流程。當在一個項目中時,你可以啟動"New"向?qū)В菏褂肅trl+N,或者在你想放置規(guī)則流的目錄上右擊,并選擇"New",然后點取->Other..."-> "Drools"->"RuleFlow file"。這將創(chuàng)建一個新的.rf文件。
接下來你會看見圖形規(guī)則流編輯器?,F(xiàn)在是切換到Drools透視圖的好時間(如果你還沒有這樣好)。這會調(diào)整用戶界面,以便于它最優(yōu)于規(guī)則。然后,確保你能夠在Elicpse窗口的底部看見了Properties視圖,因為需要用它來填充在你流程中的元素的不同屬性。如果你沒有看見Properties視圖,點取"Window"->"Show View" -> "Other..."->"General" -> Properties View打開它。
規(guī)則流編輯器由一個調(diào)色板、一個畫布和一個大綱視圖構(gòu)成。要添加新的元素到畫布,在調(diào)色板中選取你想創(chuàng)建的元素,然后點擊你喜歡的地方添加它們到畫布。例如,點出在"Components"的GUI調(diào)色板中的"RuleFlowGroup"圖標:然后你可以繪制出一些規(guī)則流組。點擊在你規(guī)則流中的一個元素,允許你設(shè)置那個元素的屬性。你也可以通過使用調(diào)色板中"Connection Creation"連接節(jié)點(只要是被允許的不同類型的節(jié)點)。
你可以繼續(xù)添加節(jié)點,并連接到你的流程,直到它表示出你想指定的業(yè)務(wù)邏輯。嘗試在應(yīng)用程序中使用它之前,你可能需要為任何缺失的信息檢查流程(通過點擊在IDE菜單條中的綠色"Check"圖標)。
4.1.2 使用XML定義流程
也可以直接使用底層XML確定流程。這些XML流程的語法是使用XML Schema定義被定義。例如,下面的XML片斷顯示了一個簡單流程, 包含了一個Start節(jié)點、一個打印"Hello World"到控制臺的 Action節(jié)點、和一個 End節(jié)點。
<?xml version="1.0" encoding="UTF-8"?>
<process xmlns="http://drools.org/drools-5.0/process"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/drools-5.0/process drools-processes-5.0.xsd"
type="RuleFlow" name="ruleflow" id="com.sample.ruleflow" package-name="com.sample" >
<header>
</header>
<nodes>
<start id="1" name="Start" x="16" y="16" />
<actionNode id="2" name="Hello" x="128" y="16" >
<action type="expression" dialect="mvel" >System.out.println("Hello World");</action>
</actionNode>
<end id="3" name="End" x="240" y="16" />
</nodes>
<connections>
<connection from="1" to="2" />
<connection from="2" to="3" />
</connections>
</process>
流程應(yīng)該嚴格地由一個<process>元素構(gòu)成。這個元素包含了有關(guān)流程的參數(shù)(它的類型、名字、id、以及包名字),并且包含了三個子部分:
一個<header> (能夠定義流程級信息的地方,如變量、全局、導入和泳道(swimlanes)的地方;一個<nodes> 部分,定義在流程中的每個節(jié)點; 一個<connections> 部分,包含在流程中所有節(jié)點間的線路。在節(jié)點部分,有一個特殊元素用于每個節(jié)點,定義各種參數(shù),以及可能的用于子元素的節(jié)點類型。
4.1.3 使用Process API定義流程
雖然推薦使用圖形編輯器或底層XML定義流程(保護你自己的內(nèi)部APIs),但是也可以直接使用Process API定義流程。最重要的流程元素被定義在包org.drools.workflow.core 和org.drools.workflow.core.node中。提供了一個"fluent API",它允許你使用工廠以可讀的方式輕松地構(gòu)建流程。最后,你可以驗證你以手動方式構(gòu)建的流程。一些如何使用fluent API構(gòu)建流程的例子被添加在下面。
4.1.3.1 例子1
這是一個簡單的基本流程例子,只使用了一個規(guī)則集(ruleset)節(jié)點。
RuleFlowProcessFactory factory =
RuleFlowProcessFactory.createProcess("org.drools.HelloWorldRuleSet");
factory
// Header
.name("HelloWorldRuleSet")
.version("1.0")
.packageName("org.drools")
// Nodes
.startNode(1).name("Start").done()
.ruleSetNode(2)
.name("RuleSet")
.ruleFlowGroup("someGroup").done()
.endNode(3).name("End").done()
// Connections
.connection(1, 2)
.connection(2, 3);
RuleFlowProcess process = factory.validate().getProcess();
你可以看見,我們開始調(diào)用了來自RuleFlowProcessFactory的靜態(tài)createProcess()方法。這個方法使用給定的id創(chuàng)建了一個新的流程,并返回給可以被用來創(chuàng)建流程的RuleFlowProcessFactory。一個典型的流程由三部分構(gòu)成。Header部分由全局元素構(gòu)成,如流程的名字、導入和變量等等。Nodes部分包含所有是流程的一部分的不同的節(jié)點。Connections部分最終互相連接這些節(jié)點,創(chuàng)建一個流程圖。
在這個例子中,Header部分包含了流程的名字,版本號和包名。在這之后,你可以添加節(jié)點給當前的流程。如果你使用自動完成(auto-completion),你可以看見,在你安排時,你可使用不同的方法創(chuàng)建每種支持的節(jié)點類型。
當你開始添加節(jié)點到流程時,在這個例子中,通過調(diào)用startNode()、ruleSetNode()和endNode()方法,你可以看見這些節(jié)點返回了一個NodeFactory,它允許你設(shè)置這些節(jié)點的屬性。一旦你完成了特殊節(jié)點的配置,done()方法返回給你當前的RuleFlowProcessFactory,如果需要,這樣你可以添加更多的節(jié)點。
當你完成節(jié)點添加時,你必須在它們之間創(chuàng)建線路連接它們。這可以通過調(diào)用方法connection來做,它將連接前面創(chuàng)建的節(jié)點。
最后,你可以通過調(diào)用validate()方法驗證產(chǎn)生的流程,并且檢索創(chuàng)建的RuleFlowProcess對象。
4.1.3.2 例子2
這個例子使用了分枝(Split)和聯(lián)合(Join)節(jié)點。
RuleFlowProcessFactory factory =
RuleFlowProcessFactory.createProcess("org.drools.HelloWorldJoinSplit");
factory
// Header
.name("HelloWorldJoinSplit")
.version("1.0")
.packageName("org.drools")
// Nodes
.startNode(1).name("Start").done()
.splitNode(2).name("Split").type(Split.TYPE_AND).done()
.actionNode(3).name("Action 1")
.action("mvel", "System.out.println(\"Inside Action 1\")").done()
.actionNode(4).name("Action 2")
.action("mvel", "System.out.println(\"Inside Action 2\")").done()
.joinNode(5).type(Join.TYPE_AND).done()
.endNode(6).name("End").done()
// Connections
.connection(1, 2)
.connection(2, 3)
.connection(2, 4)
.connection(3, 5)
.connection(4, 5)
.connection(5, 6);
RuleFlowProcess process = factory.validate().getProcess();
這顯示了一個使用了分枝和聯(lián)合節(jié)點的簡單例子。如你所見,一個分枝節(jié)點有多個輸出路線,一個聯(lián)合節(jié)點有多個輸入路線。要理解不同類型的分枝和聯(lián)合節(jié)點的行為,請參考這些節(jié)點的文檔。
4.1.3.3 例子 3
現(xiàn)在我們顯示一個更復(fù)雜的例子,使用了一個ForEach節(jié)點,在那兒我們可以嵌套節(jié)點。
RuleFlowProcessFactory factory =
RuleFlowProcessFactory.createProcess("org.drools.HelloWorldForeach");
factory
// Header
.name("HelloWorldForeach")
.version("1.0")
.packageName("org.drools")
// Nodes
.startNode(1).name("Start").done()
.forEachNode(2)
// Properties
.linkIncomingConnections(3)
.linkOutgoingConnections(4)
.collectionExpression("persons")
.variable("child", new ObjectDataType("org.drools.Person"))
// Nodes
.actionNode(3)
.action("mvel", "System.out.println(\"inside action1\")").done()
.actionNode(4)
.action("mvel", "System.out.println(\"inside action2\")").done()
// Connections
.connection(3, 4)
.done()
.endNode(5).name("End").done()
// Connections
.connection(1, 2)
.connection(2, 5);
RuleFlowProcess process = factory.validate().getProcess();
在這兒你可以看見我們?nèi)绾伟粋€ForEach節(jié)點與嵌套動作節(jié)點。注意,調(diào)用linkIncomingConnections()和linkOutgoingConnections()方法,連接ForEach節(jié)點與內(nèi)部動作節(jié)點。這些方法被用來指定在ForEach復(fù)合節(jié)點內(nèi)部的第一個節(jié)點和最后一個節(jié)點。
4.2 在你的應(yīng)用程序中使用一個流程
要能夠從你的應(yīng)用程序內(nèi)部執(zhí)行流程,有兩件事情需要你做:(1)你需要創(chuàng)建一個包含流程定義的知識庫,(2)你需要創(chuàng)建一個與流程引擎通信的會話來啟動流程,并啟動流程。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newClassPathResource("MyProcess.rf"),
ResourceType.DRF );
在添加了所有知識到構(gòu)建器后(你可以添加多個流程,甚至規(guī)則),你可以象以下這樣創(chuàng)建一個知識庫:
KnowledgeBase kbase = kbuilder.newKnowledgeBase();
注意,如果知識庫包含錯誤,這將拋出一個異常(因為它不能正確解析你的流程)。
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
ksession.startProcess("com.sample.MyProcess");
startProcess方法的參數(shù)代表你需要啟動流程的id。這個流程的id需要用流程的屬性來指定,當你點擊你的流程畫布背景時,顯示在Properties視圖中。在流程的執(zhí)行期間,如果你的流程也要求執(zhí)行規(guī)則,你也需要調(diào)用ksession.fireAllRules()方法確保規(guī)則也被執(zhí)行。就是這樣!
你也可以使用其他參數(shù),用于傳遞輸入數(shù)據(jù)到流程,使用startProcess(String processId, Map parameters)方法,它獲得了一個用名字-值(name-value)對為參數(shù)的附加集合。然后這些參數(shù)被復(fù)制到新創(chuàng)建的流程實例,作為流程的頂級變量。
你也可以在一個規(guī)則的推論內(nèi)啟動流程,或者,從一個流程的動作內(nèi)部,使用預(yù)定的kcontext參數(shù)啟動流程:
kcontext.getKnowledgeRuntime().startProcess("com.sample.MyProcess");