|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionThis Article discusses using the Drools.NET project in a Visual Studio 2008 Forms application. It goes through the steps to use the project files, as well as a brief discussion of the Drools Rule Syntax. BackgroundThere are lots of articles on the web explaining why applications should have the business logic separated from the application logic. During my time with Blaze Software, Neuron Data, and Inference Corporation, I must have given that presentation a thousand times. I am not going to go into all of that here. Instead, I will attempt to show what can be done with a Business Rules System -- even if you don’t have the six figure budgets necessary to implement a solution using one of the commercial Rules Management systems, (such as Fair Isaac’s Blaze Advisor or Ilog’s JRules -- or whatever it will be called now they have been bought by IBM.) JBOSS Rules and DROOLS.NETAn open source alternative to the commercial business rules engines is DROOLS -- re-branded now as JBOSS rules. It has recently given its internal algorithm a speed boost, and it has announced that it will support the legacy syntax used by Inference’s ART, and NASA’s CLIPS project. (as well as several other rules syntaxes.) Honestly, I was never overly fond of the ART syntax – which was too LISP-like for my tastes. However, there is no arguing that it was a very successful language, and there are a lot of examples and applications still available today for it. The main thing is that with DROOLS, you have several syntaxes to choose from, which is always a good thing. So, learning JBOSS Rules opens the doors to new techniques and architectures, but where to start? Well, if C# and .NET is your cup of tea, you might want to have a look at DROOLS.NET. Before We Start...You can find the project page here: http://droolsdotnet.codehaus.org/ Just don’t go there looking for documentation on how to use it if you are just getting started! It’s not available. Oh, they have a “Getting Started in Drools.NET” guide – but it is, strangely, full of typos. Whoever wrote it did NOT put it in Visual Studio and then cut and paste it to the document… apparently, they typed it from memory and then never bothered to test it… weird… (since that seems harder to me than putting it into Visual Studio to begin with.) Happily, we have other sources of information available. Scott McMaster has a nice article on his BLOG describing a very simple example to get you started: http://softwaredevscott.spaces.live.com/blog/cns!1A9E939F7373F3B7!621.entry This article is my somewhat expanded example which describes how rules are used in DROOLS.NET, and gives some explanation on how a rules system is implemented. It is not meant to be an in-depth tutorial, it is simply a starting point that I hope is useful. I have the full application as part of the article so you can download and follow along – that way you don’t end up with typos like the unfortunate Drools.Net developer who did the online docs ;-) I should note that I am using the commercial version of Visual Studio 2008. I have not tested this in the free version, nor in 2005 – and, though I would expect it to run there, YMMV. Using the codeI started with a simple Windows Form example that, when compiled, looks like this (when running):
SETUP For Drools.NETTo start, From there, we add 5 DLLS to your project: "IKVM.Runtime.dll" "IKVM.GNU.Classpath.dll" "drools-3.0.dll" "drools.dotnet.dll" "drools-dep.dll"
The IKVM dlls allow you to run a Java engine in a .NET world. It was a “quick and dirty” way for the developers of Drools.Net to get the code over. There are rumors of a native port in the works which would do away with these emulators, and make Drools.Net faster, but today, this is what we have. Of course, you will also need the appropriate include statements in your project: using org.drools.dotnet.compiler; using org.drools.dotnet.rule; using org.drools.dotnet; The next step is to add the rules base to your project, and set its BUILD ACTION to "Ebedded Resource". In reality, you probably shouldn’t do it that way. Having a rules file embedded in your project sort of defeats the whole purpose of having a rules file to begin with. The real power of a rules system is being able to change the rules without requiring a recompile of the project. However, when I tried to load an external rules file, I got some exceptions thrown. I am not sure why yet, and haven’t been able to track it down. For now, I am going to post this article with the embedded rules file, and hope to update it later. At least I KNOW this way WILL WORK. (UPDATE: This is now fixed... see notes at bottom of article...) The rules file, called SimpleRules.DRL is in the attached package, but it only contains 3 rules. They look like this: package MinimalDroolsFormrule "LargeOrder"when
then
endrule "OldCustomer"when
then
endrule "Discount"when
then
endA quick breakdown of the rules file is as follows: The first line simply tells Drools that it is referring to the “package” MinimalDroolsForm, which is my Windows Form namespace. Besides the Window Forms code, this namespace also includes a small “Customer Object” that looks like this: public class CustomerRecord { public String Id { get; set; } public String Status { get; set; } public int Value { get; set; } public int DaysSinceLastOrder { get; set; } public int TotalOrders { get; set; } public CustomerRecord() { } } In the rest of the rules file, I have 3 rules named “ A rule is broken down into 2 parts. You will often hear this referred to the Left Hand Side of the Rule and the Right Hand Side. Or, LHS and RHS. This is old terminology that has followed rules systems since their early PROLOG days. In Prolog, rules were expressed just that way, with the conditions placed on the left hand side, and the actions placed on the right hand side. Even though we now “stack” rules so the condition is on TOP of the action, I still find myself referring to the rule’s LHS and RHS – old habits are hard to break -- so I ask the reader’s indulgence. Anyway, the LHS of the rule, or the condition, specifies all of the things a rule must look at before it takes its specified action (sometimes referred to as the rule “Firing”.) Usually, these conditions will form something known as “patterns”. There are many ways to think of a pattern. I find most programmers have some experience with databases, so the best way to think of a pattern is that it is similar to an SQL selection set. Understand that there may be hundreds, or even thousands, of objects in a rule system. By establishing patterns, we can filter out all of the non-essential data, so the rule can look at the areas of data that are relevant to it, and act upon only that. Since we may re-use a pattern within a rule, we “name” it. This is done by putting the name to the left of the pattern. So, in the first rule, we see this: custOrder : CustomerRecord( Value > 50 )
This is all that is on the LHS of the rule. Next, we come to the action, or the RHS. We don’t do anything fancy here. We simply print out that the rules finds the order large. The next rule is more interesting. Here, we not only print the result, we also change its status to “Re-Activate.” Rule Note that in a rule, an AND condition is implied. In other words, two conditions in a rule are assumed to be ANDed. If you want to make them OR’d, you have to specify it explicitly. (Although a rule purist would tell you that there is no such thing as an OR condition in the Cognitive model… I won’t debate that here…) The second thing that is interesting, is that we have an action which calls an internal method from Drools called Modify does not do what you might think – it does not actually modify the object. That is done by the previous statement: cust.Status = "Re-Active". The Purpose of Do you see now why the second condition is needed? Otherwise, this rule would fire forever. We are changing the Status property, and asking the rules engine to check to see what rules apply for it. The The third rule seems boring again, but it actually is interesting when we run the scenarios. The reason is that none of our objects ever have the Status initially set to Re-Activate. The only way the Status can be set to Re-Activate is by the This is referred to as Forward Chaining. One rule takes an action which requires a second rule to be evaluated. It is the bread and butter of a rules system. Without forward chaining capabilities in a rules engine, all you have is a very fancy switch/case set-up. OK, back to the project… According to the documentation in the Drools.Net project web page, you have two options for starting a new rules service, but I can only get one to work. I put my code in the FORM_SHOWN event code, since I want to have feedback given to the user while the rule base is loaded. This code (minus debug printouts) looks something like this: _thisForm = this; PackageBuilder builder = new PackageBuilder(); Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MinimalDroolsForm.SimpleRules.drl"); builder.AddPackageFromDrl("MinimalDroolsForm.SimpleRules.drl", stream); Package pkg = builder.GetPackage(); ruleBase = RuleBaseFactory.NewRuleBase(); ruleBase.AddPackage(pkg); workingMemory = ruleBase.NewWorkingMemory(); workingObj = new CustomerRecord(); The first line sets a static variable that references the form itself. I use this so the rules can access the textBox on the form. Then comes the fun stuff. The Assembly.GetExecutingAssembly… is code that opens a stream to the DRL file which is embedded in my projects resource file. Then I go through the steps to load the rules file, using that stream, and putting it in the new package, and finally adding that to the rulebase. (BTW the AddPackageFromDRL function takes several seconds to run… be patient…) The last step for loading the rules base is to declare a new area of working memory. This is the area that the rules look at when they calculate which apply, and which don’t. If it ain’t in the working memory, as far as the rules are concerned, it doesn’t exist. Now, we’re ready to go, but nothing will happen until we click the “Run Rules Button”. That is when the object’s values are set, and then it is calls the function that “runs” the rules: TestRules(ruleBase, _thisForm); If you look inside this function, you will see what actually does the work: if (_currentCust == null) { _currentCust = workingMemory.assertObject(workingObj); } else { workingMemory.modifyObject(_currentCust, workingObj); } workingMemory.fireAllRules(); The reason for the IF statement is that there is no sense in creating an object more than once. If we already have it, we simply modify it. Finally, we call the unfortunately named: FireAllRules() method. (In reality, not all rules fire – just those that apply.) After the rules are done processing, I print the values of the customer object in the form’s text box, so we can verify that the rules are doing their job. To make things easier, I have three scenarios loaded, and you can see what they do by clicking on them, and hitting the run rules button. Scenario 1 is really boring. It fires no rules. Result:
Scenario 2 is interesting, because it causes the forward chaining that we discussed earlier: Result:
Scenario 3’s result may not be intuitive if you don’t understand how pattern matching works. For Scenario 3, I create TWO objects in the working memory. Result:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||