Drools Flow is a workflow and process engine that allows advanced integration of processes and rules.
This section describes how to get started with Drools Flow. It will guide you to create and exectue your first Drools Flow process.
The best way to get started is to use the Drools Eclipse IDE. This is a plugin for the Eclipse developement environment that allows users to create, execute and debug Drools processes and rules.
To get started, you need an Eclipse 3.3.x, as well as the Eclipse Graphical Editing Framework (GEF) plugin installed. Eclipse can be downloaded from http://www.eclipse.org/downloads/ (choose either the Eclipse IDE for Java Developers or the Eclipse Classic). Eclipse GEF can also be downloaded from http://www.eclipse.org/gef/downloads/ (choose the corresponding version) or by using an update site.
Download the Drools Eclipse IDE plugin from http://www.jboss.org/auth/drools/downloads.html (the latest snapshot build can also be downloaded from https://hudson.jboss.org/hudson/job/drools/lastSuccessfulBuild/artifact/trunk/target/), unzip it in your eclipse folder and (re)start Eclipse. If installation was successful, the Drools menu action should appear in the top menu bar.
You should switch to the Drools perspective within Eclipse first, as this will open all the relevant views for you. You can do this by clicking on the Open Perspective button (top right of your screen) and selecting Other ... -> Drools.
A new project wizard can be used to setup an executable project to start using processes immediately. This will setup a basic structure, classpath, sample process and execution code to get you started. To create a new Drools Project, select File -> New -> Project ... and in the Drools folder, select Drools Project. This should open the following dialog:
Give your project a name and click Next. In the following dialog you can select which elements are added to your project by default. Since we are creating a new process, deselect the first two checkboxes and select the last two. This will generate a sample process and a class to execute this process in your project.
The end result should look like this and contains:
ruleflow.rf : the process definition, which is a very simple process containing a start node (the entry points), and action node (that prints out 'Hello World') and an end node (the end of the process).
RuleFlowTest.java : a Java class that executes the process.
The necessary libraries are automatically added to the project classpath as a Drools library.
The RuleFlow editor contains a graphical representation of your process definition. It consists of nodes that are connected. The editor shows the overall control flow, while the details of each of the elements can be viewed (and edited) in the Properties View at the bottom. The editor contains a palette at the left that can be used to drag-and-drop new nodes, and an outline view at the right.
This process is a simple sequence of three nodes. The start node defines the start of the process. It is connected to an action node (called 'Hello' that simply prints out 'Hello World' to the standard output. You can see this by clicking on the Hello node and checking the action property in the properties view below. This node is then connected to an end node, signalling the end of the process.
While it is probably easier to edit processes using the graphical editor, user can also modify the underlying XML directly. The XML for our sample process is shown below (note that we did not include the graphical information here for simplicity). The process element contains parameters like the name and id of the process, and consists of three main subsections: a header (where information like variables, globals and imports can be defined), the nodes and the connections.
<?xml version="1.0" encoding="UTF-8"?> <process xmlns="http://drools.org/drools-4.0/process" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://drools.org/drools-4.0/process drools-processes-4.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>
To execute this process, right-click on RuleFlowTest.java and select Run As - Java Application. When the process in executed, the following output should appear on the console:
Hello World
If you look at the RuleFlowTest code (see below), you will see that executing a process requires a few steps:
You should first create a knowledge base. A knowledge base contains all the process definitions and rules that are relevant in your application. This knowledge base can be created only once and can be reused.
Next, you should create a session to interact with the engine.
Finally, you can start a new process instance of our process by invoking the startProcess("ruleflowId") method of the session. This will start the execution of your process. Since our process does not contain any wait states, the process will execute until it is completed in this case, passing through the start, action and end node.
package com.sample; import java.io.InputStreamReader; import java.io.Reader; import org.drools.RuleBase; import org.drools.RuleBaseFactory; import org.drools.StatefulSession; import org.drools.compiler.PackageBuilder; import org.drools.rule.Package; /** * This is a sample file to launch a ruleflow. */ public class RuleFlowTest { public static final void main(String[] args) { try { //load the process RuleBase ruleBase = createKnowledgeBase(); // create a new session StatefulSession session = ruleBase.newStatefulSession(); // start a new process instance session.startProcess("com.sample.ruleflow"); } catch (Throwable t) { t.printStackTrace(); } } /** * Creates the knowledge base by loading the process definition. */ private static RuleBase createKnowledgeBase() throws Exception { // create a builder PackageBuilder builder = new PackageBuilder(); // load the process Reader source = new InputStreamReader( RuleFlowTest.class.getResourceAsStream("/ruleflow.rf")); builder.addProcessFromXml(source); // create the knowledge base Package pkg = builder.getPackage(); RuleBase ruleBase = RuleBaseFactory.newRuleBase(); ruleBase.addPackage(pkg); return ruleBase; } }
Congratulations, you have successfully executed your first process! You can now start experimenting and designing your own process by modifying our example. Note that you can validate your process by clicking on the green check box action in the upper toolbar. Processes will also be validated upon save and errors will be shown in the error view. Or you can continue reading our documentation to learn about our more advanced features.
Drools already provides some functionality to define the order in which rules should be executed, like salience, activation groups, etc. When dealing with (possibly a lot of) large rule-sets, managing the order in which rules are evaluated might become complex. Ruleflow allows you to specify the order in which rule sets should be evaluated by using a flow chart. This allows you to define which rule sets should be evaluated in sequence or in parallel, to specify conditions under which rule sets should be evaluated, etc. This chapter contains a few ruleflow examples.
A rule flow is a graphical description of a sequence of steps that the rule engine needs to take, where the order is important. The ruleflow can also deal with conditional branching, parallelism, synchonization, etc.
To use a ruleflow to describe the order in which rules should be evaluatied, you should first group rules into rulefow-groups using the ruleflow-group rule attribute ("options" in the GUI). Then you should create a ruleflow graph (which is a flow chart) that graphically describe the order in which the rules should be considered (by specifying the order in which the ruleflow-groups should be evaluated).
rule 'YourRule' ruleflow-group 'group1' when ... then ... end
This rule will then be placed in the ruleflow-group called "group1".
The above rule flow specifies that the rules in the group "Check Order" must be executed before the rules in the group "Process Order". This means that only rules which are marked as having a ruleflow-group of "Check Order" will be considered first, and then "Process Order". That's about it. You could achieve similar results with either using salience (setting priorities, but this is harder to maintain, and makes the time-relationship implicit in the rules), or agenda groups. However, using a ruleflow makes the order of processing explicit, almost like a meta-rule, and makes managing more complex situations a lot easier. The various elements that can be used inside a ruleflow will be explained in more detail later.
Ruleflows can only be created by using the graphical ruleflow editor which is part of the Drools plug-in for Eclipse. Once you have set up a Drools project (check the IDE chapter if you do not know how to do this), you can start adding ruleflows. When in a project, use "control+N" to launch the new wizard, or right-click the directory you would like to put your ruleflow in and select "New ... Other ...":
Choose the section on "Drools" and then pick "RuleFlow file". This will create a new .rf file.
Next you will see the graphical ruleflow editor. Now would be a good time to switch to the "Drools perspective" (if you haven't done so already) - this will tweak the UI so it is optimal for rules. Then ensure that you can see the "properties" panel down the bottom of the Eclipse window, as it will be necessary to fill in the different properties of the elements in your ruleflow. If you cannot see the properties view, open it using the Menu Window - Show View - Other ..., and under the General folder select the Properties view.
The RuleFlow editor consists of a palette, a canvas and an outline view. To add new elements to the canvas, select the element you would like to create in the palette and then add them to the canvas by clicking on the preferred location. For example, click on the RuleFlowGroup icon in the Component Pallette of the GUI - you can then draw a few rule flow groups. Clicking on an element in your ruleflow allows you to set the properties of that element.
Click on a ruleflow group, and you should see the following:
You can see here you set the visible name, but you also need to set the actual group name that is used in the rules.
Next step is to join the groups together (if its a simple sequence of steps) - you use this by using "create connection" from the component palette. You should also create an "End" node (also from the component palette).
In practice, if you are using ruleflow, you will most likely be doing more then setting a simple sequence of groups to progress though. You are more likely modeling branches of processing. In this case you use "Split" and "Join" items from the component pallette. You use connections to connect from the start to ruleflow groups, or to Splits, and from splits to groups, joins etc. (i.e. basically like a simple flow chart that models your processing). You can work entirely graphically until you get the graph approximately right.
The above flow is a more complex example. This example is an insurance claim processing rule flow. A description: Initially the claim data validation rules are processed (these check for data integrity and consistency, that all the information is there). Next there is a decision "split" - based on a condition which the rule flow checks (the value of the claim), it will either move on to an "auto-settlement" group, or to another "split", which checks if there was a fatality in the claim. If there was a fatality then it determines if the "regular" of fatality specific rules will take effect. And so on. What you can see from this is based on a few conditions in the rule flow the steps that the processing takes can be very different. Note that all the rules can be in one package - making maintenance easy. You can separate out the flow control from the actual rules.
Split types (referring to the above): When you click on a split, you will see the above properties panel. You then have to choose the type: AND, OR, and XOR. The interesting ones are OR and XOR: if you choose OR, then any of the "outputs" of the split can happen (ie processing can proceed in parallel down more then one path). If you chose XOR, then it will be only one path.
If you choose OR or XOR, then in the row that has constraints, you will see a button on the right hand side that has "..." - click on this, and you will see the constraint editor. From this constraint editor, you set the conditions which the split will use to decide which "output path" will be chosen.
Choose the output path you want to set the constraints for (eg Autosettlement), and then you should see the following constraint editor:
This is a text editor where the constraints (which are like the condition part of a rule) are entered. These constraints operate on facts in the working memory (eg. in the above example, it is checking for claims with a value of less than 250). Should this condition be true, then the path specified by it will be followed.
Once you have a valid ruleflow (you can check its valid by pressing the green "tick" icon in the IDE), you can add a rule flow to a package just like a drl. However, the IDE creates two versions of your ruleflow: one containing the ruleflow definition (*.rfm) and one containing additional graphical information (*.rf). When adding a ruleflow to a package, you should make sure that you are adding the .rfm file to your ruleflow (and not the .rf file).
Reader rfm = ... (rule flow reader, select your .RFM file here) packageBuilder.addRuleFlow(rfm);
Alternatively, you can upload the .rf file to the BRMS (as a ruleflow asset) and it will automatically be included in packages that are deployed from it.
Ruleflows are only executed if you explicitly state that they should be executed. This is because you could potentially define a lot of ruleflows in your package and the engine has no way to know when you would like to start each of these. To activate a particular ruleflow, you will need to start the process by calling the startProcess method on the working memory. For example, if you want to start a particular workflow after you have asserted your facts into the working memory, use:
workingMemory.startProcess("ID_From_your_Ruleflow_properties");
(The ruleflow id can be specified in the properties view when you click the background canvas of your ruleflow). And then call fireAllRules(). This will start executing rules, taking the order specified in the ruleflow into account. Thats it !
You can also start a ruleflow process from within a rule consequence using
drools.getWorkingMemory().startProcess("ID_From_your_Ruleflow_properties");
A ruleflow is a flow chart where different types of nodes are linked using connections. It has the following properties: a (unique) id, a (display) name and a version. Global and imports can be defined that can be reused throughout the entire process. You can also specify how the connections are visualized on the canvas using the connection layout property:
manual always draws your connections as lines going straight from their start to end point (with the possibility to use intermediate break points)
shortest path is similar, but it tries to go around any obstacles is might encounter between the start and end point (to avoid lines crossing nodes)
manhatten draws connections by only using horizontal and vertical lines
RuleFlow supports different types of nodes:
Start: the start of the ruleflow. A ruleflow should have exactly one start node. The start node cannot have incoming cnnections and should have one outgoing connection. It contains one property "name" which is the display name of the node. Whenever ruleflow process is started, the ruleflow will start executing here and automatically continue to the first node linked to this start node.
End: the end of the ruleflow. A ruleflow should have one or more end nodes. The end node should have one incoming connection and cannot have outgoing connections. It contains one property "name" which is the display name of the node. When an end node is reached in the ruleflow, the ruleflow is terminated. If a ruleflow is terminated, all nodes that are still active in this ruleflow are cancelled first (which is possible if parallel paths are used).
RuleSet: represents a set of rules. The rules are evaluated when the node is reached. A RuleFlowGroup node should have one incoming connection and one outgoing connection. It contains a property "name" which is the display name of the node, and the property "ruleflow-group" which is used to specify the name of the ruleflow-group that represents the set of rules of this RuleFlowGroup node. Rules can use the ruleflow-group attribute to become part of a ruleflow group. When a RuleSet node is reached in the ruleflow, the engine will start executing rules that are part of the corresponding ruleflow-group (if any). Execution will automatically continue to the next node if there are no more active rules in this ruleflow-group. This means that, during the execution of a ruleflow-group, it is possible that new activations belonging to the currently active ruleflow-group are added to the agenda due to changes made to the facts by the other rules. Note that the ruleflow will immediately continue with the next node if it encounters a ruleflow-group where there are no active rules at that point. If the ruleflow-group was already active, the ruleflow-group will remain active and exeution will only continue if all active rules of the ruleflow-group has been completed.
Split: allows you to create branches in your ruleflow. A split node should have one incoming connection and two or more outgoing connections. It contains a property "name" which is the display name of the node. There are three types of splits currently supported:
AND means that the control flow will continue in all outgoing connections simultaneously (paralellism).
XOR means that exactly one of the outgoing connections will be chosen (decision). Which connection is decided by evaluating the constraints that are linked to each of the outgoing connections. Constraints are specified using the same syntax as the left-hand side of a rule. The constraint with the lowest priority number that evaluates to true is selected. Note that you should always make sure that at least one of the outgoing connections will evaluate to true at runtime (the ruleflow will throw an exception at runtime if it cannot find at least one outgoing connection). For example, you could use a connection which is always true (default) with a high priority number to specify what should happen if none of the other connections can be taken.
OR means that all outgoing connections whose condition evaluates to true are selected. Conditions are similar to the XOR split, except that the priorities are not taken into account. Note that you should make sure that at least one of the outgoing connections will evaluate to true at runtime (the ruleflow will throw an exception at runtime if it cannot find an outgoing connection).
Join: allows you to synchronize multiple branches. A join node should have two or more incoming connections and one outgoing connection. It contains a property "name" which is the display name of the node. There are three types of splits currently supported:
AND means that is will wait until all incoming branches are completed before continuing.
XOR means that it continues if one of its incoming branches has been completed.
Discriminator means that it continues if one of its incoming branches has been completed.
Event wait (milestone): represents a wait state. An event wait should have one incoming connection and one outgoing connection. It contains a property "name" which is the display name of the node, and the property "constraint" which specifies how long the ruleflow should wait in this state before continuing. For example, a milestone constraint in an order entry application might specify that the ruleflow should wait until (a fact in the working memory specifies that) no more errors are found in the given order. Constraints are specified using the same syntax as the left-hand side of a rule. When a wait node is reached in the ruleflow, the engine will check the associated constraint. If the constraint evaluates to true directly, the flow will continue imediately. Otherwise, the flow will continue if the constraint is satisfied later on, for example when a facts in the working memory is inserted, updated or removed.
SubProcess: represents the invocation of another process from withing this ruleflow. A sub-process node should have one incoming connection and one outgoing connection. It contains a property "name" which is the display name of the node, and the property "processId" which specifies the id of the process that should be executed. When a SubProcess node is reached in the ruleflow, the engine will start the process with the given id. If the property "Wait for completion" is true, the subflow node will only continue if that subflow process has terminated its execution (completed or aborted); otherwise it will continue immediately after starting the sub-process. If the property "independent" is true, the sub-process is started as an independent process, which means that the subflow process will not be terminated if this process reaches an end node; otherwise the active sub-process will be cancelled on termination (or abortion) of the process. A SubProcess can also define in- and out-mappings for variables. The value of variables in this process with given variable name in the in-mappings will be used as parameters (with the associated parameter name) when starting the process. The value of the variables in the sub-process with the given variable name in the out-mappings will be copied to the variables of this process when the sub-process has been completed. Note that can only use out-mappings when "Wait for completion" is set to true.
Action: represents an action that should be executed in this ruleflow. An action node should have one incoming connection and one outgoing connection. It contains a property "name" which is the display name of the node, and the property "action" which specifies the action that should be executed. The action should specify which dialect is used to specify the action (e.g. Java or MVEL), and the actual action code. The action code can refer to any globals and the special 'drools' variable which implements KnowledgeHelper, and can for example be used to access the working memory (drools.getWorkingMemory()). When an action node is reached in the ruleflow, it will execute the action and continue with the next node.
WorkItem: represents an (abstract) item of work that should be executed in this ruleflow. A WorkItem node should have one incoming connection and one outgoing connection. It contains a property "name" which is the display name of the node. The type of work is uniquely identified by the work item name. When a WorkItem node is reached in the ruleflow, it will execute start the associated work item. If the property "Wait for completion" is true, the WorkItem node will only continue if the created work item has terminated its execution (completed or aborted); otherwise it will continue immediately after starting the work item. Each type of work item can define parameters that describe that type of work. For example, an email node (work item name "Email" will define properties like 'from', 'to', 'subject' and 'text'. The user can either fill in values for these parameters directly, or define a parameter mapping that will copy the value of the given variable in this process to the given parameter (if both are specified, the mapping will have precedence). Each type of work can also define result parameters that will be returned after the work item has been completed. A result mapping can be used to copy the value of the given result parameter to the given variable in this process. Note that can only use out-mappings when "Wait for completion" is set to true. The user can define their own types of work items (see the section about pluggable work items).
Timer: represents a timer that can trigger one or multiple times after a given period of time. A Timer node should have one incoming connection and one outgoing connection. It contains a property "name" which is the display name of the node. The "TimerDelay" property specifies how long (in milliseconds) the timer should wait before triggering the first time. The "Timerperiod" specifies the timer will between two subsequenct triggers. A period of 0 means that the timer should only be executed once. When a timer node is reached in the ruleflow, it will execute the associated timer. The timer is cancelled if the timer node is cancelled (e.g. by completing the running process).
Drools Flow is a workflow and process engine that allows advanced integration of processes and rules. This chapter gives an overview of how to integrate rules and processes, ranging from simple to advanced.
Workflow languages that depend purely on process constructs (like nodes and connections) to describe the business logic of (a part of) an application tend to be quite complex. While these workflow constructs are very well suited to describe the overall control flow of an application, it can be very difficult to describe complex logic and exceptional situations. Therefore, executable processes tend to become very complex. We believe that, by extending a process engine with support for declarative rules in combination with these regular process constructs, this complexity can be kept under control.
Simplicity: Complex decisions are usually easier to specify using a set of rules. Rules can describe complex business logic more easily by using an advanced constraint language. Multiple rules can be combined, each describing a part of the business logic.
Agility: Rules and processes can have a separate life cycle. This means that for example we can change the rules describing some crucial decision points without having to change the process itself. Rules can be added, removed or modified to fine-tune the behaviour of the process to the constantly evolving requirements and environment.
Different scope: Rules can be reused accross processes or outside processes. Therefore, your business logic is not locked inside your processes.
Declarative: Focus on describing 'what' instead of 'how'.
Granularity: It is easy to write simple rules that handle specific circumstances. Processes more suited to describe the overall control flow but tend to become very complex if they also need to describe a lot of exceptional situations.
Data-centric: Rules can easily handle large data sets.
Performance: Rule evaluation is optimized.
Advanced condition and action language: Rule languages supports advanced features like custom functions, collections, not, exists, for all, etc.
Higher-level: Using DSLs, business editors, decision tables, decision trees, etc. your business logic could be described in a way that can be understood (and possibly even modified) by business users.
Drools Flow combines a process and a rules engine in one software product. This offers several advantages (compared to trying to loosely coupling an existing process and rules product):
Simplicity: Easier for end user to combine both rules and processes.
Encapsulation: Sometimes close integration between processes and rules is beneficial.
Performance: No unnecessary passing, transformation or synchronization of data
Learning curve: Easier to learn one product.
Manageability: Easier to manage one product, rules and processes can be similar artefacts in a larger knowledge repository.
Integration of features: We provide an integrated IDE, audit log, web-based management platform, repository, debugging, etc.
Workflow languages describe the order in which activities should be performed using a flow chart. A process engine is responsible for selecting which activities should be executed based on the current state of the executing processes. Rules on the other hand are composed of a set of conditions that describe when a rule is applicable and an action that is executed when the rule is applicable. The rules engine is then responsible for evaluating and executing the rules. It decides which rules need to be executed based on the current state of the application.
Workflow processes are very good at describing the overall control flow of (possibly long-running) applications. However, processes that are used to define complex business decisions or contain a lot of exceptional situations or need to respond to various external events tend to become very complex. Rules on the other hand are very good at describing complex decisions and reasoning about large amounts of data or events. It is however not trivial to define long-running processes using rules.
In the past, users were forced to choose between defining their business logic using either a process or rules engine. Problems that required complex reasoning about large amounts of data used a rules engine, while users that wanted to focus on describing the control flow of their processes were forced to use a process engine. However, businesses nowadays might want to combine both processes and rules in order to be able to define all their business logic in the format that best suits their needs.
Basically, both a rules and a process engine will derive the next steps that need to be executed by looking at its knowledge base (a set of rules or processes respectively) and the current known state of the application (the data in the working memory or the state of the executing process instances respectively). If we want to integrate rules and processes, we need an engine that can decide the next steps taking into account the logic that is defined inside both the processes and the rules.
It is very difficult (and probably very inefficient as well) to extend a process engine to also take rules into account: the process engine would need to check for rules that might need to be executed at every step and would have to keep the data that is used by the rules engine up to date. However, it is not that difficult to 'teach' a rules engine about processes. If the current state of the processes is also inserted as part of the data the rules engine reasons about, and we 'learn' the rules engine how to derive the next steps of an executing process, the rules engine will then be able to derive the next steps taking both rules and processes into account.
From the process perspective, this means that there is an inversion of control. In a normal proces engine, the engine is in full control and derives the next steps based on the current state of the process instance. If needed, it can contact external services to retrieve additional information (e.g. invoke a rules engine to request a decision), but it solely decides which steps to take, and is responsible for executing these steps.
However, only our extended rules engine (that can reason about both rules and processes) is capable of deriving the next steps taking both rules and processes into account. If a part of the process needs to be executed, the rules engine will request the process engine to execute this step. Once this step has been performed, the process engine returns control to the rules engine to again derive the next steps. This means that the control on what to do next has been inverted: the process engine itself no longer decides the next step to take but our extended rules engine will be in control, notifying the process engine when to execute the next step.
The drools-examples project contains a sample process (org.drools.examples.process.order) that illustrates some of the advantages of being able to combine processes and rules. This process describes an order application where incoming orders are validated, possible discount are calculated and shipping of the goods is requested.
Drools Flow can easily include a set of rules as part of the process. The rules that need to be evaluated should be grouped in a ruleflow group (using the ruleflow-group rule attribute) and a RuleSet node can be used to trigger the evaluation of these rules in your process. This example uses two RuleSet nodes in the process: one for the validation of the order and one for calculating the discount. For example, one of the rules for validiting an order looks like this (note the ruleflow-group attribute, which makes sure that this rule is evaluated as part of the RuleSet node with the same ruleflow-group):
Rules can be used for expressing and evaluating complex constraints in your process. For example, when a decision should be made which execution paths should be selected at a split, rules could be used to define these conditions. Similarly, a wait state could use a rule to define how long to wait. This example uses rules for deciding the next action after validating the order. If the order contains errors, a sales representative should try to correct the order. Orders with a value > 1000$ are more important and a senior sales representative should follow up the order. All other orders should just proceed normally. An decision node is used to select one of these alternatives, and rules are used to describe the constraints for each of them:
Human tasks can be used in a process to describe work that needs to be executed by a human actor. Which actor could be based on the current state of the process, and the history. Assignment rules can be used to describe how to the actor based on this information. These assignment rules will then be applied automatically whenever a new human task needs to be executed.
Rules can be used for describing exceptional situations and how to respond to these situations. Adding all this information in the control flow of the main process would make the basic process much more complex. Rules can be used to handle each of these situations separately, without making the core process more complex. It also makes it much easier to adapt existing processes to take new unanticipated events into account.
The process defines the overall control flow. Rules could be used to add additional concerns to this process without making the overall control flow more complex. For example, rules could be defined to log certain information during the execution of the process. The original process is not altered and all logging functionality is cleanly modularized as a set of rules. This greatly improves reusability (allows users to easily apply the same strategy on different processes), readability (control flow of the original process is still the same) and modifiability (you can easily add, remove or change the logging strategy by adding, removing or changing the rules, the process should not be modified).
Rules can be used to dynamically fine-tune the behaviour of your processes. For example, if a problem is encountered at runtime with one of the processes, new rules could be added at runtime to log additional information or handle specific cases of the process. Once the problem is solved or the circumstances have changed, these rules can easily be removed again. Based on the current status, different strategies could be selected dynamically. For example, based on the current load of all the services, rules could be used to optimize the process to the current load. This process contains a simple example that allows you to dynamically add or remove logging for the 'Check Order' task. When the 'Debugging output' checkbox in the main application window is checked, a rule is dynamically loaded to add a logging statement to the console whenever the 'Check Order' task is requested. Unchecking the box will dynamically remove the rule again.
Processes and rules are integrated in the Drools Eclipse IDE. Both processes and rules are simply considered as different types of business logic, but are managed almost identical. For example, loading a process or a set of rules into the engine is very similar:
Our audit log also contains an integrated view, showing how rules and processes are influencing each other. For example, a part of the log shows how the '5% discount' rule is executed as part of the 'Calculate Discount' node.
Rules do not need to be defined using the core rule language syntax, but they also can be defined using our more advanced rule editors like domain-specific languages, decision tables, guided editors, etc. Our examples defines a domain-specific language for describing assignment rules, based on the type of task, its properties, the process it is defined in, etc. This makes the assignment rules much more understandable for non-experts.
One of the goals of our unified rules and processes framework is to allow users to extend the default programming constructs with domain-specific extensions that simplify development in a particular application domain. While Drools has been offering constructs to create domain-specific rule languages for some time now, this tutorial describes our first steps towards domain-specific process languages.
Most process languages offer some generic action (node) construct that allows plugging in custum user actions. However, these actions are usually low-level, where the user is required to write custom code to implement the work that should be incorporated in the process. The code is also closely linked to a specific target environment, making it difficult to reuse the process in different contexts.
Domain-specific languages are targeted to one particular application domain and therefore can offer constructs that are closely related to the problem the user is trying to solve. This makes the processes and easier to understand and self-documenting. We will show you how to define domain-specific work items, which represent atomic units of work that need to be executed. These work items specify the work that should be executed in the context of a process in a declarative manner, i.e. specifying what should be executed (and not how) on a higher level (no code) and hiding implementation details.
So we want work items that are:
domain-specific
declarative (what, not how)
high-level (no code)
customizable to the context
Users can easily define their own set of domain-specific work items and integrate them in our process language(s). For example, the next figure shows an example of a process in a healthcare context. The process includes domain-specific work items for ordering nursing tasks (e.g. measuring blood pressure), prescribing medication and notifying care providers.
Let's start by showing you how to include a simple work item for sending notifications. A work item represent an atomic unit of work in a declarative way. It is defined by a unique name and additional parameters that can be used to describe the work in more detail. Work items can also return information after they have been executed, specified as results. Our notification work item could thus be defined using a work definition with four parameters and no results:
Name: "Notification" Parameters From [String] To [String] Message [String] Priority [String]
All work definitions must be specified in one or more configuration files in the project classpath, where all the properties are specified as name-value pairs. Parameters and results are maps where each parameter name is also mapped to the expected data type. Note that this configuration file also includes some additional user interface information, like the icon and the display name of the work item. (We use MVEL for reading in the configuration file, which allows us to do more advanced configuration files). Our MyWorkDefinitions.conf file looks like this:
import org.drools.process.core.datatype.impl.type.StringDataType; [ // the Notification work item [ "name" : "Notification", "parameters" : [ "Message" : new StringDataType(), "From" : new StringDataType(), "To" : new StringDataType(), "Priority" : new StringDataType(), ], "displayName" : "Notification", "icon" : "icons/notification.gif" ] // add more work items here ... ]
The Drools Configuration API can be used to register work definition files for your project using the drools.workDefinitions property, which represents a list of files containing work definitions (separated usings spaces). For example, include a drools.rulebase.conf file in the META-INF directory of your project and add the following line:
drools.workDefinitions = MyWorkDefinitions.conf
Once our work definition has been created and registered, we can start using it in our processes. The process editor contains a separate section in the palette where the different work items that have been defined for the project appear.
Using drag and drop, a notification node can be created inside your process. The properties can be filled in using the properties view.
Apart from the properties defined by for this work item, all work items also have these three properties:
Parameter Mapping: Allows you map the value of a variable in the process to a parameter of the work item. This allows you to customize the work item based on the current state of the actual process instance (for example, the priority of the notification could be dependent of some process-specific information).
Result Mapping: Allows you to map a result (returned once a work item has been executed) to a variable of the process. This allows you to use results in the remainder of the process.
Wait for completion: By default, the process waits until the requested work item has been completed before continuing with the process. It is also possible to continue immediately after the work item has been requested (and not waiting for the results) by setting "wait for completion" to false.
The Drools engine contains a WorkItemManager that is responsible for executing work items whenever necessary. The WorkItemManager is responsible for delegating the work items to WorkItemHandlers that execute the work item and notify the WorkItemManager when the work item has been completed. For executing notification work items, a NotificationWorkItemHandler should be created (implementing the WorkItemHandler interface):
package com.sample; import org.drools.process.instance.WorkItem; import org.drools.process.instance.WorkItemHandler; import org.drools.process.instance.WorkItemManager; public class NotificationWorkItemHandler implements WorkItemHandler { public void executeWorkItem(WorkItem workItem, WorkItemManager manager) { // extract parameters String from = (String) workItem.getParameter("From"); String to = (String) workItem.getParameter("To"); String message = (String) workItem.getParameter("Message"); String priority = (String) workItem.getParameter("Priority"); // send email EmailService service = ServiceRegistry.getInstance().getEmailService(); service.sendEmail(from, to, "Notification", message); // notify manager that work item has been completed manager.completeWorkItem(workItem.getId(), null); } public void abortWorkItem(WorkItem workItem, WorkItemManager manager) { // Do nothing, notifications cannot be aborted } }
This WorkItemHandler sends a notification as an email and then immediate notifies the WorkItemManager that the work item has been completed. Note that not all work items can be completed directly. In cases where executing a work item takes some time, execution can continue asynchronously and the work item manager can be notified later. In these situations, it might also be possible that a work item is being aborted before it has been completed. The abort method can be used to specify how to abort such work items.
WorkItemHandlers should be registered at the WorkItemManager, using the following API:
workingMemory.getWorkItemManager().registerWorkItemHandler( "Notification", new NotificationWorkItemHandler());
Decoupling the execution of work items from the process itself has the following advantages:
The process is more declarative, specifying what should be executed, not how.
Changes to the environment can be implemented by adapting the work item handler. The process itself should not be changed. It is also possible to use the same process in different environments, where the work item handler is responsible for integrating with the right services.
It is easy to share work item handlers across processes and projects (which would be more difficult if the code would be embedded in the process itself).
Different work item handlers could be used depending on the context. For example, during testing or simulation, it might not be necessary to actually execute the work items. The next section shows an example of how to use specialized work item handlers during testing.
Customizable execution depending on context, easier to manage changes in environment (by changing handler), sharing processes accross contexts (using different handlers), testing, simulation (custom test handlers)
Our process framework is based on the (already well-known) idea of a Process Virtual Machine (PVM), where the process framework can be used as a basis for multiple process languages. This allows users to more easily create their own process languages, where common services provided by the process framework (e.g. persistence, audit) can be (re)used by the process language designer. Processes are represented as a graph of nodes, each node describing a part of the process logic. Different types of nodes are used for expressing different kinds of functionality, like creating or merging parallel flows (split and join), invoking a sub process, invoking external services, etc. One of our goals is creating a truly pluggable process language, where language designers can easily plug in their own node implementations.
This section describes how to debug processes. This means that the current state of your running processes can be inspected and visualized during the execution. We use a simple example throughout this section to illustrate the debugging capabilities. The example will be introduced first, followed by an illustration on how to use the debugging capabilities.
Our example contains two processes and some rules (used inside the ruleflow groups):
The main process contains some of the most common nodes: a start and end node (obviously), two ruleflow groups, an action (that simply prints a string to the default output), a milestone (a wait state that is trigger when a specific Event is inserted in the working memory) and a subprocess.
The SubProcess simply contains a milestone that also waits for (another) specific Event in the working memory.
There are only two rules (one for each ruleflow group) that simply print out either a hello world or goodbye world to default output.
We will simulate the execution of this process by starting the process, firing all rules (resulting in the executing of the hello rule), then adding the specific milestone events for both the milestones (in the main process and in the subprocess) and finally by firing all rules again (resulting in the executing of the goodbye rule). The console will look something like this:
Hello World Executing action Goodbye cruel world
We now add four breakpoints during the execution of the process (in the order in which they will be encountered):
At the start of the consequence of the hello rule
Before inserting the triggering event for the milestone in the main process
Before inserting the triggering event for the milestone in the subprocess
At the start of the consequence of the goodbye rule
When debugging the application, one can use the following debug views to track the execution of the process:
The working memory view, showing the contents (data) in the working memory.
The agenda view, showing all activations in the agenda.
The global data view, showing the globals.
The default Java Debug views, showing the current line and the value of the known variables, and this both for normal Java code as for rules.
The process instances view, showing all running processes (and their state).
The audit view, showing the audit log.
The process instances view shows the currently running process instances. The example shows that there is currently one running process (instance), currently executing one node (instance), i.e. RuleSet node. When double-clicking a process instance, the process instance viewer will graphically show the progress of the process instance. At each of the breakpoints, this will look like:
At the start of the consequence of the hello rule, only the hello ruleflow group is active, waiting on the execution of the hello rule:
Once that rule has been executed, the action, the milestone and the subprocess will be triggered. The action will be executed immediately, triggering the join (which will simply wait until all incomming connections have been triggered). The subprocess will wait at the milestone. So, before inserting the triggering event for the milestone in the main process, there now are two process instances, looking like this:
When triggering the event for the milestone in the main process, this will also trigger the join (which will simply wait until all incomming connections have been triggered). So at that point (before inserting the triggering event for the milestone in the subprocess), the processes will look like this:
When triggering the event for the milestone in the subprocess, this process instance will be completed and this will also trigger the join, which will then continue and trigger the goodbye ruleflow group, as all its incomming connections have been triggered. Firing all the rules will trigger the breakpoint in the goodbye rule. At that point, the situation looks like this:
After executing the goodbye rule, the main process will also be completed and the execution will have reached the end.
For those who want to look at the result in the audit view, this will look something like this [Note: the object insertion events might seem a little out of place, which is caused by the fact that they are only logged after (and never before) they are inserted, making it difficult to exactly pinpoint their location.]