if
...then
statements with a framework that gave us the same benefits of configurability, readability, and reuse that we already enjoy in other areas? This article suggests using the Drools rules engine as a framework to solve the problem.
The sample code below gives a sample of the problem we're trying to avoid. It shows some business logic in a typical Java application.
We've all come across similar (or even more complex) business logic. While this has been the standard way of implementing business logic in Java, there are many problems with it.
J2EE/EJB and "inversion of control" frameworks (such as Spring, Pico, and Avalon) give us the ability to organize our code at a high level. While they are very good at providing reusability, configuration, and security, none of them would replace the "spaghetti code" in the above example. Ideally, whatever framework we choose will be compatible with not only J2EE applications, but also "normal" Java (J2SE--Standard Edition) programs, and most of the widely used presentation and persistence frameworks. Such a framework should allow us to do the following:
An additional problem is that while there are only so many ways to organize web pages and database access, business logic tends to differ widely between applications. Our framework should be able to cope with this and still promote code reuse. Ideally, our application would be "frameworks all the way down." By using frameworks in this way, we can a large amount of our application "out of the box," allowing us to write only the parts that add value for the customer.
How are we going to solve this problem? One solution that is gaining traction is to use a rule engine. Rule engines are frameworks for organizing business logic that allow the developer to concentrate on things that are known to be true, rather than the low-level mechanics of making decisions.
Often, business users are more comfortable with expressing things that they know to be true, than to express things in an if
...then
format. Examples of things that you might hear from a business expert are:
By focusing on what we know to be true, rather than the mechanics of how to express it in Java code, the above statements are clearer than our previous code sample. Still, clear as they may be, we still need a mechanism to apply these rules to the facts that we know and get a decision. Such a mechanism is a rule engine.
JSR 94, the javax.rules
API, sets a common standard for interacting with rule engines, much as JDBC allows us to interact with varying databases. What JSR-94 does not specify is how the actual rules are written, leaving plenty of choice among the most widely used Java rule engines:
Imagine this scenario: minutes after reading this article, your boss asks your to prototype a stock trading application. As the business users still haven't fully defined the business logic, you think it a good idea to implement it using a rules engine. The final system will be accessible over an intranet and will need to communicate with back-end database and messaging systems. To get started, download the Drools framework (with dependencies). Create a new project in your favorite IDE and make sure all of the .jars are referenced in it, as per Figure 1. This screenshot is Eclipse-based, but the setup will be similar for other IDEs.
Figure 1. Libraries needed to run Drools
Due to the huge potential losses if our stock trading system went amok, it's vital that we have some sort of simulator to put our system through its paces. Such a simulator also gives you confidence that the decisions made by the system are those that are intended, even after rule changes are made. We'll borrow some tools from the Agile toolbox and use JUnit as a framework for our simulations.
The first code we write is the JUnit Test/simulator, as per the following listing. Even if we can't test every combination of values likely to be input into our application, some tests are better than none at all. In this example, all of our files and classes (including unit tests) are in one folder/package, but in reality, you would implement a proper package and folder structure. We'd also use Log4j instead of the System.out
calls in the sample code.
import junit.framework.TestCase;
/*
* JUnit test for the business rules in the
* application.
*
* This also acts a 'simulator' for the business
* rules - allowing us to specify the inputs,
* examine the outputs and see if they match our
* expectations before letting the code loose in
* the real world.
*/
public class BusinessRuleTest extends TestCase {
/**
* Tests the purchase of a stock
*/
public void testStockBuy() throws Exception{
//Create a Stock with simulated values
StockOffer testOffer = new StockOffer();
testOffer.setStockName("MEGACORP");
testOffer.setStockPrice(22);
testOffer.setStockQuantity(1000);
//Run the rules on it
BusinessLayer.evaluateStockPurchase(testOffer);
//Is it what we expected?
assertTrue(
testOffer.getRecommendPurchase()!=null);
assertTrue("YES".equals(
testOffer.getRecommendPurchase()));
}
}
This is a basic JUnit test, as we know that our (very simple!) system should buy all stocks with a price of less than 100 Euro. Obviously, this won't compile without our data holding class (StockOffer.java) and our business layer class (BusinessLayer.java). These are provided in the following listings.
/** * Facade for the Business Logic in our example. * * In this simple example, all our business logic * is contained in this class but in reality it * would delegate to other classes as required. */ public class BusinessLayer { /** * Evaluate whether or not it is a good idea * to purchase this stock. * @param stockToBuy * @return true if the recommendation is to buy * the stock, false if otherwise */ public static void evaluateStockPurchase (StockOffer stockToBuy){ return false; } }
The StockOffer
class looks like this:
/** * Simple JavaBean to hold StockOffer values. * A 'Stock offer' is an offer (from somebody else) * to sell us a Stock (or Company share). */ public class StockOffer { //constants public final static String YES="YES"; public final static String NO="NO"; //Internal Variables private String stockName =null; private int stockPrice=0; private int stockQuantity=0; private String recommendPurchase = null; /** * @return Returns the stockName. */ public String getStockName() { return stockName; } /** * @param stockName The stockName to set. */ public void setStockName(String stockName) { this.stockName = stockName; } /** * @return Returns the stockPrice. */ public int getStockPrice() { return stockPrice; } /** * @param stockPrice The stockPrice to set. */ public void setStockPrice(int stockPrice) { this.stockPrice = stockPrice; } /** * @return Returns the stockQuantity. */ public int getStockQuantity() { return stockQuantity; } /** * @param stockQuantity to set. */ public void setStockQuantity(int stockQuantity){ this.stockQuantity = stockQuantity; } /** * @return Returns the recommendPurchase. */ public String getRecommendPurchase() { return recommendPurchase; } }
We run BusinessRuleTest
through the JUnit extension of our favorite IDE. If you're not familiar with JUnit, more information can be found at . Not surprisingly, our test fails at the second assertion, shown in Figure 2, as we don't (yet) have the appropriate business logic in place. This is reassuring, as it shows that our simulator/unit tests are highlighting the problems that they should.
Figure 2. JUnit test results
At this point, we need to write some business logic that says, "If the stock price is less than 100 Euro, then we should buy it." To do this, we will modify BusinessLayer.java to read:
import java.io.IOException;
import org.drools.DroolsException;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.event.DebugWorkingMemoryEventListener;
import org.drools.io.RuleBaseLoader;
import org.xml.sax.SAXException;
/**
* Facade for the Business Logic in our example.
*
* In this simple example, all our business logic
* is contained in this class but in reality it
* would delegate to other classes as required.
* @author default
*/
public class BusinessLayer {
//Name of the file containing the rules
private static final String BUSINESS_RULE_FILE=
"BusinessRules.drl";
//Internal handle to rule base
private static RuleBase businessRules = null;
/**
* Load the business rules if we have not
* already done so.
* @throws Exception - normally we try to
* recover from these
*/
private static void loadRules()
throws Exception{
if (businessRules==null){
businessRules = RuleBaseLoader.loadFromUrl(
BusinessLayer.class.getResource(
BUSINESS_RULE_FILE ) );
}
}
/**
* Evaluate whether or not to purchase stock.
* @param stockToBuy
* @return true if the recommendation is to buy
* @throws Exception
*/
public static void evaluateStockPurchase
(StockOffer stockToBuy) throws Exception{
//Ensure that the business rules are loaded
loadRules();
//Some logging of what is going on
System.out.println( "FIRE RULES" );
System.out.println( "----------" );
//Clear any state from previous runs
WorkingMemory workingMemory
= businessRules.newWorkingMemory();
//Small ruleset, OK to add a debug listener
workingMemory.addEventListener(
new DebugWorkingMemoryEventListener());
//Let the rule engine know about the facts
workingMemory.assertObject(stockToBuy);
//Let the rule engine do its stuff!!
workingMemory.fireAllRules();
}
}
This class now has some important methods:
loadRules()
, which loads the rules from the BusinessRules.drl file.
evaluateStockPurchase()
, which evaluates these business rules. Some points to note about this method are:
RuleSet
over and over (as business rules in memory are stateless).
WorkingMemory
for every evaluation, as this is our knowledge of what we know to be true at this time. We use assertObject()
to place known facts (as Java Object
s) into this memory.
fireAllRules()
method on the working memory class causes the rules to be evaluated and updated (in this case, stock offer). Before we can run the example again, we need to create our BusinessRules.drl file, as follows:
<?xml version="1.0"?>
<rule-set name="BusinessRulesSample"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs
="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation
="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">
<!-- Import the Java Objects that we refer
to in our rules -->
<java:import>
java.lang.Object
</java:import>
<java:import>
java.lang.String
</java:import>
<java:import>
net.firstpartners.rp.StockOffer
</java:import>
<!-- A Java (Utility) function we reference
in our rules-->
<java:functions>
public void printStock(
net.firstpartners.rp.StockOffer stock)
{
System.out.println("Name:"
+stock.getStockName()
+" Price: "+stock.getStockPrice()
+" BUY:"
+stock.getRecommendPurchase());
}
</java:functions>
<rule-set>
<!-- Ensure stock price is not too high-->
<rule name="Stock Price Low Enough">
<!-- Params to pass to business rule -->
<parameter identifier="stockOffer">
<class>StockOffer</class>
</parameter>
<!-- Conditions or 'Left Hand Side'
(LHS) that must be met for
business rule to fire -->
<!-- note markup -->
<java:condition>
stockOffer.getRecommendPurchase() == null
</java:condition>
<java:condition>
stockOffer.getStockPrice() < 100
</java:condition>
<!-- What happens when the business
rule is activated -->
<java:consequence>
stockOffer.setRecommendPurchase(
StockOffer.YES);
printStock(stockOffer);
</java:consequence>
</rule>
</rule-set>
This rules file has several interesting parts:
StockOffer
class), one or more conditions that need to be fulfilled, and a consequence that is carried out if and when the conditions are met. Having modified and compiled our code, we run the JUnit test simulations again. This time, the business rules are called, our logic evaluates correctly, and our tests pass, as seen in Figure 3. Congratulations--you've just built your first rule-based application!
Figure 3. Successful JUnit test
Fresh from building the application, you demonstrate the prototype above to the business users, and they remember a few more rules that they forgot to mention earlier. One of the new rules is that we shouldn't trade stocks where the quantity is a negative number (<0). "No problem," you say, and return to your desk, secure in the knowledge that you can quickly evolve your system.
The first thing you do is to update your simulator, and add the following code to BusinessRuleTest.java:
/**
* Tests the purchase of a stock
* makes sure the system will not accept
* negative numbers.
*/
public void testNegativeStockBuy()
throws Exception{
//Create a Stock with our simulated values
StockOffer testOffer = new StockOffer();
testOffer.setStockName("MEGACORP");
testOffer.setStockPrice(-22);
testOffer.setStockQuantity(1000);
//Run the rules on it
BusinessLayer
.evaluateStockPurchase(testOffer);
//Is it what we expected?
assertTrue("NO".equals(
testOffer.getRecommendPurchase()));
}
This tests for the new rule described by the business users. If we run this JUnit test, our new test fails, as expected. We need to add a new rule to our .drl file, as follows.
<!-- Ensure that negative prices
are not accepted-->
<rule name="Stock Price Not Negative">
<!-- Parameters we can pass into
the business rule -->
<parameter identifier="stockOffer">
<class>StockOffer</class>
</parameter>
<!-- Conditions or 'Left Hand Side' (LHS)
that must be met for rule to fire -->
<java:condition>
stockOffer.getStockPrice() < 0
</java:condition>
<!-- What happens when the business rule
is activated -->
<java:consequence>
stockOffer.setRecommendPurchase(
StockOffer.NO);
printStock(stockOffer);
</java:consequence>
</rule>
This rule is similar in format to the previous one, expect that our <java:condition>
is different (testing for negative numbers) and the <java:consequence>
sets the recommend purchase to No
. We run our unit tests/simulator again, and this time the test passes.
At this point, if you're used to procedural programming (like most Java programmers), you may be scratching your head: here we have a file containing two separate business rules, yet we haven't told the rule engine which is more important. However, our stock price (of -22) satisfies both rules (i.e., it is less than 0 and it is less than 100). Despite this, we get the correct result, even if we swap the order of the rules around. How does this work?
The extract of the console output below helps us to see what is going on. We see that both rules are firing (the [activationfired]
line), and that the Recommend Buy
is first set to Yes
and then to No
. How does Drools know to fire these rules in the correct order? If you look at the Stock Price Low Enough
rule, you will see that one of the conditions is that recommendPurchase()
is null. This is enough for the Drools rule engine to decide that the Stock Price Low Enough
rule should be fired before the Stock Price Not Negative
rule. This process is called conflict resolution.
FIRE RULES
----------
[ConditionTested: rule=Stock Price Not Negative;
condition=[Condition: stockOffer.getStockPrice()
< 0]; passed=true; tuple={[]}]
[ActivationCreated: rule=Stock Price Not Negative;
tuple={[]}]
[ObjectAsserted: handle=[fid:2];
object=net.firstpartners.rp.StockOffer@16546ef]
[ActivationFired: rule=Stock Price Low Enough;
tuple={[]}]
[ActivationFired: rule=Stock Price Not Negative;
tuple={[]}]
Name:MEGACORP Price: -22 BUY:YES
Name:MEGACORP Price: -22 BUY:NO
If you're a procedural programmer, no matter how clever you think this is, you still may not trust it completely. That is why we have our unit tests/simulator: "hard" JUnit tests (using normal Java code) ensure that the rule engine makes its decisions along the lines we want it to. (And doesn't spend billions on worthless stock!) At the same time, the power and the flexibility of our rule engine allows us to quickly develop the business logic.
Later on, we will see more sophisticated forms of conflict resolution.
Now the folks on the business side are really impressed and are starting to think through the possible options. They've come across a problem with stocks of XYZ Corp and have decided to implement a new rule: Only buy stocks of XYZ Corp if they are less than 10 Euro.
As before, you add the test to our simulator and include the new business rule in our rules file, as per the following listings. First, we add a new method to BusinessRuleTest.java:
/**
* Makes sure the system will buy stocks
* of XYZ corp only if it really cheap
*/
public void testXYZStockBuy() throws Exception{
//Create a Stock with our simulated values
StockOffer testOfferLow = new StockOffer();
StockOffer testOfferHigh = new StockOffer();
testOfferLow.setStockName("XYZ");
testOfferLow.setStockPrice(9);
testOfferLow.setStockQuantity(1000);
testOfferHigh.setStockName("XYZ");
testOfferHigh.setStockPrice(11);
testOfferHigh.setStockQuantity(1000);
//Run the rules on it and test
BusinessLayer.evaluateStockPurchase(
testOfferLow);
assertTrue("YES".equals(
testOfferLow.getRecommendPurchase()));
BusinessLayer.evaluateStockPurchase(
testOfferHigh);
assertTrue("NO".equals(
testOfferHigh.getRecommendPurchase()));
}
Next, we need a new <rule>
in BusinessRules.drl:
<rule name="XYZCorp" salience="-1">
<!-- Parameters we pass to rule -->
<parameter identifier="stockOffer">
<class>StockOffer</class>
</parameter>
<java:condition>
stockOffer.getStockName().equals("XYZ")
</java:condition>
<java:condition>
stockOffer.getRecommendPurchase() == null
</java:condition>
<java:condition>
stockOffer.getStockPrice() > 10
</java:condition>
<!-- What happens when the business
rule is activated -->
<java:consequence>
stockOffer.setRecommendPurchase(
StockOffer.NO);
printStock(stockOffer);
</java:consequence>
</rule>
Note that in the business rules file, after the rule name, we set our salience
to -1
(i.e., the lowest priority of all of the rules we have specified so far). Most of the rules in our system conflict, meaning Drools must make some decision on the order in which to fire rules, given that the conditions for all of the rules will be met. The default way of deciding is:
Salience
: A value we assign, as per the above listing.
Recency
: How many times we have used a rule.
Complexity
: Specific rules with more complicated values fire first.
LoadOrder
: The order in which rules are loaded. If we did not specify the saliency of our rule in this example, what would happen is:
Recommend Buy
flag would be set to No
).
Recommended Buy
flag to yes
. This would give a result that we don't want. However, since our example does set the saliency factor, the test and our business rules work as expected.
While most of the time, writing clear rules and setting the saliency will give enough information to Drools for it to choose the proper order in which to fire rules, sometimes we want to change the entire manner in which rule conflicts are resolved. An example of how to change this is given below, where we tell the rule engine to fire the simplest rules first. A word of warning: be careful when changing conflict resolution, as it can fundamentally change the behavior of the rule engine--a lot of problems can be solved first with clear and well-written rules.
//Generate our list of conflict resolvers
ConflictResolver[] conflictResolvers =
new ConflictResolver[] {
SalienceConflictResolver.getInstance(),
RecencyConflictResolver.getInstance(),
SimplicityConflictResolver.getInstance(),
LoadOrderConflictResolver.getInstance()
};
//Wrap this up into one composite resolver
CompositeConflictResolver resolver =
new CompositeConflictResolver(
conflictResolvers);
//Specify this resolver when we load the rules
businessRules = RuleBaseLoader.loadFromUrl(
BusinessLayer.class.getResource(
BUSINESS_RULE_FILE),resolver);
For our simple application, driven by JUnit tests, we don't need to alter the way the Drools resolves rule conflicts. It is useful to know how conflict resolution works, especially when your application grows to meet more complex and demanding requirements.
This article demonstrated a problem that most programmers have had to face: how to put some order on the complexity of business logic. We demonstrated a simple application using Drools as a solution and introduced the notion of rule-based programming, including how these rules are resolved at runtime. Later on, a follow-up article will take these foundations and show how to use them in an enterprise Java application.
Paul Browne , based in Dublin, Ireland, has been consulting in enterprise Java with FirstPartners.net for almost seven years.
These days enterprise Java could almost put you to sleep. How many hundreds of J2EE-EJB web applications have been written that capture information from a web page and store it in a database? What really keeps developers awake at night is trying to write and maintain the complex business logic in their applications. This is a problem not only for new applications, but increasingly, for long-lived, business-critical apps whose internal logic needs to change frequently, often at very short notice.
In an earlier article, "Give Your Business Logic a Framework with Drools," I introduced the Drools framework and showed how it could be used to organize complicated business logic. Drools replaced many tangled if ... then
statements with a simple set of things known to be true. If you are ever in a meeting with business customers, and your head hurts with the complexity of what they want you to implement, then maybe you should consider a rule engine such as Drools. This article will show you how you can do this in an enterprise Java application.
Most enterprise Java developers already have their favorite frameworks. In no particular order, these include presentation frameworks (Struts, JSF, Cocoon, and Spring), persistence frameworks (JDO, Hibernate, Cayenne, and Entity Beans) and structural frameworks (EJB, Spring again, Pico, and Excalibur), as well as many others. Each framework does one very useful thing (or more), and gives developers a lot of instant "out of the box" functionality. Deploying an application using frameworks means you avoid a lot of the boring bits and concentrate on what is really needed.
Until now, there was a gap in what the frameworks were able to do, in that business logic had no framework. Tools like EJB and Spring are good, but have little to say about how to organize your if ... then
statements! Adding Drools to your developer toolbox means that it is now possible to build an application with "frameworks all the way down." Figure 1 shows a diagram of such an application.
Figure 1. Frameworks for Java applications
This article will build on what we already know of the Drools framework and allow us to build such an application.
It's almost a cliche in software engineering to say that "if you have a hammer, everything looks like a nail." While rule engines can solve a lot of problems for us, it is worth considering whether a rule engine is really appropriate for our enterprise Java application. Some questions to ask are:
If you're writing an enterprise application, chances are that it will need to scale to hundreds, if not thousands, of users. You know that existing Java and J2EE applications can do this, but how will a application using Drools cope with this pressure? The answer is "surprisingly well." While most developers hate to "lose control" and rely on other people's code (i.e., a framework), consider the points below--not only should your application be as fast as "traditional" coding methods, but Drools may even make your application run faster:
if ... then
statements with an optimized network. It is important to note that the Rete algorithm involves a tradeoff between using more memory to reduce delays at run time. While this isn't a factor in most modern servers, we wouldn't yet recommend deploying Drools on your mobile phone! Most enterprise Java applications are accessed using a web interface, and one of the most widely adopted web-presentation frameworks is Struts, from Apache. Ideally, we'll write our application so that the presentation layer knows about the business layer underneath, but not the other way around. This has the advantage not only of allowing us to change the presentation framework at a later date (e.g., to an Ajax or web service interface), but also means the code examples give should be readily applicable to other web frameworks like Spring.
The following code snippet demonstrates how to call the business logic tier (using the rule engine) from the web presentation layer. The code uses the results to decide which page to display. In this sample, we use a Struts action, but the code is similar for any other web framework or even a servlet or a JSP page. This snippet works with a supporting struts-config.xml, JSP pages to post/display data, and a way of generating the WAR file for deployment. The snippet shows how to integrate the rule engine with the web framework.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import BusinessLayer;
/**
* Sample Struts action with Pseudocode
*/
public class SampleStrutsAction extends Action{
/**
* Standard Struts doPerfom method
*/
public ActionForward doPerform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws InvalidEntryPointException {
//Local Variables
StockOffer userOffer =null;
//Get any previous values from the session
userOffer=(StockOffer)request.getSession()
.getAttribute("PREVIOUS_STOCK_OFFER");
//create this object if it is null
if (null==userOffer){
userOffer = new StockOffer();
}
//Update with the incoming values
//These values match those on the form
userOffer.setStockName(request.
getParameterValue("STOCK_NAME"));
userOffer.setStockPrice(request
.getParameterValue("STOCK_PRICE"));
userOffer.setStockQuantity(request
.getParameterValue("STOCK_QTY"));
//Reset the output value
userOffer.setRecommendPurchase(null);
//Call the Business Layer
BusinessLayer
.evaluateStockPurchase(userOffer);
//Forward to the appropriate page
if ("YES".equals(
testOffer.getRecommendPurchase()){
return mapping.findForward("YES_WEB_PAGE");
}
//otherwise default to the no page
return mapping.findForward("NO_WEB_PAGE");
}
}
There are a couple of things going on this sample. Often, we build up the data we need from the user over several web pages, so this sample shows how we can achieve this by retrieving the StockOffer
object that we have previously stored in the web server session. Next, we update the StockOffer
with any values that the user may have changed on the web page. We then reset the recommendPurchase
flag to clear any previous results before we call the business logic layer. Finally, we take the response of the business logic and use it to decide which page to forward the user to.
In this example, note how we split the business logic (yes/no on whether or not to buy a stock) from the presentation logic (decide which page to go to). This allows us to reuse our business rules across several different applications In addition, take look at how the state information (i.e., things that the user has already told us) is stored in the StockOffer
object/web server session, and not in the business layer. By keeping the business layer stateless in this way, we make the entire application much more scalable and performant.
So far, our application has a web presentation layer and a rules engine for the business layer, but no means of getting data to and from a database. This section gives an example of how to do this. We base our example on the Data Access Object (DAO) pattern, where we encapsulate all code that "talks" to the database (or back-end data source) in one pluggable, configurable class. As such, the example is applicable to other persistence frameworks, such as Hibernate and Cayenne.
Some important points about the way we want to organize the data layer are:
To implement our simple Data Access Object, we create three new objects: StockNameDao
, DaoImplementation
, and DaoFactory
.
StockNameDao
is an interface that defines two methods: getStockNames()
returns a list of the stock names that we deal with, and isOnStockList()
checks that a given stock is on the list of stocks that we deal with. Our business layer will call these methods as and when it needs the information.
DaoImplementation
is an actual implementation of StockNameDao
. In this case the values are hard-coded, but we could have queried a database or accessed an information system like Bloomberg via a web service.
DaoFactory
is what we use to create an appropriate instance of StockNameDao
. The advantage this approach has over creating the class directly is that it allows us to configure what DAO implementation we use at runtime (frameworks like Spring are especially good at this). One factory can return many types of DAOs (e.g., StockNameDao
, StockPriceDao
, StockHistoryDao
), which means we can pass in our DaoFactory
, and let the individual rules decide on the data and DAOs that they require.
Here's what the StockNameDao
interface looks like:
/**
* Defines a Data Access Object - a non data
* source specific way of obtaining data.
*/
public interface StockNameDao {
/**
* Get a list of stock names for the application
* @return String[] array of stock names
*/
public String [] getStockNames();
/**
* Check if our stock is on the list
* @param stockName
* @return
*/
public boolean isOnStockList(String stockName);
}
And here's the DaoImplementation
:
/**
* Concrete Definition of a Data Access Object
*/
public class DaoImplementation
implements StockNameDao {
/**
* Constructor with package level access only
* to encourage use of factory method
*
*/
DaoImplementation(){}
/**
* Get a list of stock names for the app.
* This is a hard coded sample
* normally we would get this from
* a database or other datasource.
* @return String[] array of stock names
*/
public String[] getStockNames() {
String[] stockNames=
{"XYZ","ABC","MEGACORP","SOMEOTHERCOMPANY"};
return stockNames;
}
/**
* Check if our stock is on the list
* @param stockName
* @return true / false as appropriate
*/
public boolean isOnStockList(String stockName){
//Get our list of stocks
String stockList[] = getStockNames();
//Loop and see if our stock is on it
// done this way for clarity . not speed!
for (int a=0; a<stockList.length;a++){
if(stockList[a].equals(stockName)){
return true;
}
}
//Default return value
return false;
}
}
The simple DaoFactory
just returns a DaoImplementation
:
package net.firstpartners.rp;
/**
* Factory Method to get the Data Access Object.
* Normally we could replace this with a
* framework like Spring or Hibernate
*/
public class DaoFactory {
/**
* Get the stock name Dao
* This sample is hardcoded - in reality
* we would make this configurable / cache
* instances of the Dao as appropriate
* @return an instance of StockNameDao
*/
public static StockNameDao getStockDao(){
return new DaoImplementation();
}
}
Now that we have our simple DAO implementation to serve as our database layer, how do we integrate it with the Drools business layer? The updated business rules file, BusinessLayer.xml, shows us how.
<?xml version="1.0"?>
<rule-set name="BusinessRulesSample"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs="
http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="
http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">
<!-- Import the Java Objects that
we refer to in our rules -->
<java:import>
java.lang.Object
</java:import>
<java:import>
java.lang.String
</java:import>
<java:import>
net.firstpartners.rp.StockOffer
</java:import>
<java:import>
net.firstpartners.rp.DaoFactory
</java:import>
<java:import>
net.firstpartners.rp.StockNameDao
</java:import>
<!-- Application Data not associated -->
<!-- with any particular rule -->
<!-- In this case it's our factory -->
<!-- object which gives us back -->
<!-- a handle to whatever Dao (Data -->
<!-- access object) that we need -->
<application-data
identifier="daoFactory">DaoFactory
</application-data>
<!-- A Java (Utility) function -->
<! we reference in our rules -->
<java:functions>
public void printStock(
net.firstpartners.rp.StockOffer stock)
{
System.out.println(
"Name:"+stock.getStockName()
+" Price: "+stock.getStockPrice()
+" BUY:"+stock.getRecommendPurchase());
}
</java:functions>
<!-- Check for XYZ Corp-->
<rule name="XYZCorp" salience="-1">
<!-- Parameters we can pass into-->
<!-- the business rule -->
<parameter identifier="stockOffer">
<class>StockOffer</class>
</parameter">
<!-- Conditions that must be met for -->
<!-- business rule to fire -->
<java:condition>
stockOffer.getStockName().equals("XYZ")
</java:condition>
<java:condition>
stockOffer.getRecommendPurchase() == null
</java:condition>
<java:condition>
stockOffer.getStockPrice() > 10
</java:condition>
<!-- What happens when the business -->
<!-- rule is activated -->
<java:consequence>
stockOffer.setRecommendPurchase(
StockOffer.NO);
printStock(stockOffer);
</java:consequence>
</rule>
<!-- Ensure that negative prices -->
<!-- are not accepted -->
<rule name="Stock Price Not Negative">
<!-- Parameters we can pass into the -->
<!-- business rule -->
<parameter identifier="stockOffer">
<class>StockOffer</class>
</parameter>
<!-- Conditions for rule to fire -->
<java:condition>
stockOffer.getStockPrice() < 0
</java:condition>
<!--When rule is activated then ... -->
<java:consequence>
stockOffer.setRecommendPurchase
(StockOffer.NO);
printStock(stockOffer);
</java:consequence>
</rule>
<!-- Check for Negative Prices-->
<rule name="Stock Price Low Enough">
<!-- Parameters for the rule -->
<parameter identifier="stockOffer">
<class>StockOffer</class>
</parameter>
<!-- Now uses Dao to get stock list -->
<java:condition>
daoFactory.getStockDao().isOnStockList(
stockOffer.getStockName())
</java:condition>
<java:condition>
stockOffer.getRecommendPurchase() == null
</java:condition>
<java:condition>
stockOffer.getStockPrice() < 100
</java:condition>
<!-- When rule is activated do this -->
<java:consequence>
stockOffer.setRecommendPurchase(
StockOffer.YES);
printStock(stockOffer);
</java:consequence>
</rule>
</rule-set>
There are several changes to this file to integrate the data access layer with our business rules:
<java:import>
statements to reference the StockNameDao
, DaoImplementation
, and DaoFactory
classes that we added to the system.
<application-data>
, which assigns an instance of the DaoFactory
class to a variable. <application-data>
tags are similar to parameters, except they apply to all business rules, and not just one.
Stock Price Low Enough
rule has a new condition, which uses the DaoFactory
and StockNameDao
to check if the stock is on the list of those that we deal with. We run our BusinessRulesTest
(simulator) again. The simulator/unit tests run OK, since even though we have changed the structure of the program, we haven't (yet) changed what it does. From looking at the output logs, we can see that our business rules are using StockNameDao
as part of their evaluations, and that DaoImplementation.isOnStockList()
is being called.
While this example shows the reading of information from a data source, the principles are the same for writing information, if that is what a rule has decided should be done. The differences would be that our DAO would have a setSomeInformation()
method, and that the method would be called in the <java:consequence>
part of the business rule, once the specific conditions had been met.
In this article, we showed that most Java server applications have three tiers: presentation, business logic, and data persistence. While the use of frameworks is widely accepted in the presentation and persistence layers, until now no framework has been available to encapsulate low-level business logic. As we've seen in the examples, Drools and JSR-94 are ideal candidates for reducing the complexity and speeding the development of Java applications. I hope that these examples inspire you to take a closer look at rule engines, and that they save many hours of development and maintenance time in your applications.
Paul Browne , based in Dublin, Ireland, has been consulting in enterprise Java with FirstPartners.net for almost seven years.
支æŒBI的开æºå·¥å…äh•°é‡ä¼—多,但是大多数的工具都是å釿Ÿæ–¹é¢çš„。例如,CloverETLåé‡ETLåQŒJPivotåé‡å¤šç»´åˆ†æžå±•现åQŒMondrian是OLAPæœåŠ¡å™¨ã€‚è€ŒBeeã€Pentahoå’ŒSpagoBI½{‰é¡¹ç›®åˆ™é’ˆå¯¹å•†åŠ¡æ™ø™ƒ½é—®é¢˜æä¾›äº†å®Œæ•´çš„解决æ–ÒŽ(gu¨©)¡ˆã€?/font>
ETL 工具
ETLå¼€æºå·¥å…·ä¸»è¦åŒ…括CloverETLå’ŒOctupus½{‰ã€?
åQ?åQ‰CloverETL是一个Javaçš„ETL框架åQŒç”¨æ¥è{æ¢ç»“构化的数æ®ï¼Œæ”¯æŒå¤šç§å—符集之间的转æ¢åQˆå¦‚ASCIIã€UTF-8å’ŒISO-8859-1½{‰ï¼‰åQ›æ”¯æŒJDBCåQŒåŒæ—¶æ”¯æŒdBaseå’ŒFoxProæ•°æ®æ–‡äšgåQ›æ”¯æŒåŸºäºŽXMLçš„è{æ¢æ˜q°ã€?
(2)Octupus是一个基于Javaçš„ETL工具åQŒå®ƒä¹Ÿæ”¯æŒJDBCæ•°æ®æºå’ŒåŸÞZºŽXMLçš„è{æ¢å®šä¹‰ã€‚Octupusæä¾›é€šç”¨çš„æ–¹æ³•进行数æ®è{æ¢ï¼Œç”¨æˆ·å¯ä»¥é€šè¿‡å®žçŽ°è½¬æ¢æŽ¥å£æˆ–者ä‹É用Jscriptä»£ç æ¥å®šä¹‰è{æ¢æµ½E‹ã€?
OLAPæœåŠ¡å™?
(1)Lemur主è¦é¢å‘HOLAPåQŒè™½ç„‰™‡‡ç”¨C++¾~–写åQŒä½†æ˜¯å¯ä»¥è¢«å…¶ä»–è¯è¨€çš„ç¨‹åºæ‰€è°ƒç”¨ã€‚Lemur支æŒåŸºæœ¬çš„æ“ä½œï¼Œå¦‚åˆ‡ç‰‡ã€åˆ‡å—和旋è{½{‰åŸºæœ¬æ“作ã€?
(2)Mondriané¢å‘ROLAP包å«4层:(x¨¬)表示层ã€è®¡½Ž—层ã€èšé›†å±‚ã€å˜å‚¨å±‚ã€?
â—?表示层:(x¨¬)指最¾lˆå‘ˆçŽ°åœ¨ç”¨æˆ·æ˜„¡¤ºå™¨ä¸Šçš„以å?qi¨¢ng)与用户之间的交互,有许多方法æ¥å±•现多维数æ®åQŒåŒ…括数æ®é€è§†è¡¨ã€é¥¼ã€æŸ±ã€çº¿çж图ã€?
â—?计算层:(x¨¬)分æžã€éªŒè¯ã€æ‰§è¡ŒMDX查询ã€?
â—?èšé›†å±‚:(x¨¬)一个èšé›†æŒ‡å†…å˜ä¸ä¸€¾l„计½Ž—å€?cell)åQŒè¿™äº›å€¼é€šè¿‡¾l´åˆ—æ¥é™åˆ¶ã€‚计½Ž—层å‘é€å•元请求,如果è¯äh±‚ä¸åœ¨¾~“å˜ä¸ï¼Œæˆ–者ä¸èƒ½é€šè¿‡æ—‹è{èšé›†å¯¼å‡ºçš„è¯åQŒé‚£ä¹ˆèšé›†å±‚å‘å˜å‚¨å±‚å‘é€è¯·æ±‚。èšåˆå±‚是一个数æ®ç¼“冲层åQŒä»Žæ•°æ®åº“æ¥çš„å•元数æ®ï¼ŒèšåˆåŽæä¾›ç»™è®¡ç®—å±‚ã€‚èšåˆå±‚的主è¦ä½œç”¨æ˜¯æé«˜¾pÈ»Ÿçš„æ€§èƒ½ã€?
â—?å˜å‚¨å±‚:(x¨¬)æä¾›èšé›†å•元数æ®å’Œç»´è¡¨çš„æˆå‘˜ã€‚åŒ…æ‹¬ä¸‰¿U需è¦å˜å‚¨çš„æ•°æ®åQŒåˆ†åˆ«æ˜¯äº‹å®žæ•°æ®ã€èšé›†å’Œ¾l´ã€?
OLAP客户�
JPivot是JSPé£Žæ ¼çš„æ ‡½{‘Öº“åQŒç”¨æ¥æ”¯æŒOLAP表,使用户å¯ä»¥æ‰§è¡Œå…¸åž‹çš„OLAPæ“作åQŒå¦‚切片ã€åˆ‡å—ã€ä¸Šé’…R€ä¸‹é’ȉ。JPivot使用MondrianæœåŠ¡å™¨ï¼Œåˆ†æž¾l“æžœå¯ä»¥å¯¼å‡ºä¸ºExcel或PDFæ–‡äšgæ ¼å¼ã€?
æ•°æ®åº“管ç†ç³»¾l?
主è¦çš„å¼€æºå·¥å…·åŒ…括MonetDBã€MySQLã€MaxDBå’ŒPostgreSQL½{‰ã€‚这些数æ®åº“éƒ½è¢«è®¾è®¡ç”¨æ¥æ”¯æŒBI环境。MySQLã€MaxDBå’ŒPostgreSQL凿”¯æŒå•å‘的数æ®å¤åˆ¶ã€‚BizGres™å¹ç›®çš„目的在于ä‹ÉPostgreSQLæˆäØ“(f¨´)æ•°æ®ä»“库å’?BIçš„å¼€æºæ ‡å‡†ã€‚BizGres为BI环境构å¾ä¸“用的完整数æ®åº“òq›_°ã€?
完整的BIå¼€æºè§£å†Ïx–¹æ¡?
1.Pentaho å…¬å¸çš„Pentaho BI òq›_°
它是一个以‹¹ç¨‹ä¸ÞZ¸å¿ƒçš„ã€é¢å‘è§£å†Ïx–¹æ¡ˆçš„æ¡†æž¶åQŒå…·æœ‰å•†åŠ¡æ™ºèƒ½ç»„ä»¶ã€‚BI òq›_°æ˜¯ä»¥‹¹ç¨‹ä¸ÞZ¸å¿ƒçš„åQŒå…¶ä¸æž¢æŽ§åˆ¶å™¨æ˜¯ä¸€ä¸ªå·¥ä½œæµå¼•擎。工作æµå¼•擎使用‹¹ç¨‹å®šä¹‰æ¥å®šä¹‰åœ¨ BI òq›_°ä¸Šæ‰§è¡Œçš„å•†åŠ¡æ™ø™ƒ½‹¹ç¨‹ã€‚æµ½E‹å¯ä»¥å¾ˆå®ÒŽ(gu¨©)˜“被定åˆÓž¼Œä¹Ÿå¯ä»¥æ·»åŠ æ–°çš„æµ½E‹ã€‚BI òq›_°åŒ…嫾l„äšg和报表,用以分枘q™äº›‹¹ç¨‹çš„æ€§èƒ½ã€‚BI òq›_°æ˜¯é¢å‘è§£å†Ïx–¹æ¡ˆçš„åQŒåã^å°çš„æ“ä½œæ˜¯å®šä¹‰åœ¨‹¹ç¨‹å®šä¹‰å’ŒæŒ‡å®šæ¯ä¸ªæ´»åŠ¨çš„ action 文档里。这些浽E‹å’Œæ“作共åŒå®šä¹‰äº†ä¸€ä¸ªå•†åŠ¡æ™ºèƒ½é—®é¢˜çš„è§£å†³æ–ÒŽ(gu¨©)¡ˆã€‚è¿™ä¸?BI 解决æ–ÒŽ(gu¨©)¡ˆå¯ä»¥å¾ˆå®¹æ˜“地集æˆåˆ°åã^å°å¤–部的商业‹¹ç¨‹ã€‚一个解å†Ïx–¹æ¡ˆçš„定义å¯ä»¥åŒ…å«ä»ÀL„æ•°é‡çš„æµ½E‹å’Œæ“作ã€?
BIòq›_°åŒ…括一ä¸?BI 框架ã€BI ¾l„äšgã€ä¸€ä¸?BI 工作å°å’Œæ¡Œé¢æ”¶äšg½Ž±ã€‚BI å·¥ä½œå°æ˜¯ä¸€å¥—设计和½Ž¡ç†å·¥å…·åQŒé›†æˆåˆ°Eclipse环境。这些工具å…许商业分æžäh员或开å‘äh员创建报表ã€äÈA表盘ã€åˆ†æžæ¨¡åž‹ã€å•†ä¸šè§„则和 BI ‹¹ç¨‹ã€‚Pentaho BI òq›_°æž„å¾äºŽæœåС噍ã€å¼•擎和¾l„äšg的基¼‹€ä¹‹ä¸ŠåQŒåŒ…括J2EE æœåС噍ã€å®‰å…¨ä¸Žæƒé™æŽ§åˆ¶ã€portalã€å·¥ä½œæµã€è§„则引擎ã€å›¾è¡¨ã€å作ã€å†…容管ç†ã€æ•°æ®é›†æˆã€å¤š¾l´åˆ†æžå’Œ¾pÈ»Ÿå»ºæ¨¡½{‰åŠŸèƒ½ã€‚è¿™äº›ç»„ä»¶çš„å¤§éƒ¨åˆ†æ˜¯åŸÞZºŽæ ‡å‡†çš„,å¯ä‹É用其他äñ”哿›¿æ¢ä¹‹ã€?
2.ObjectWeb
该项目近日å‘布了SpagoBi 1.8版本。SpagoBi 是一‹Æ‘ÖŸºäºŽMondrain+JProvitçš„BIæ–ÒŽ(gu¨©)¡ˆåQŒèƒ½å¤Ÿé€šè¿‡OpenLaszlo产生实时报表åQŒäØ“(f¨´)å•†åŠ¡æ™ø™ƒ½™å¹ç›®æä¾›äº†ä¸€ä¸ªå®Œæ•´å¼€æºçš„解决æ–ÒŽ(gu¨©)¡ˆåQŒå®ƒæ¶ëŠ›–了一个BI¾pÈ»Ÿæ‰€æœ‰æ–¹é¢çš„功能åQŒåŒ…括:(x¨¬)æ•°æ®æŒ–æŽ˜ã€æŸ¥è¯¢ã€åˆ†æžã€æŠ¥å‘Šã€Dashboard仪表æ¿ç‰½{‰ã€‚SpagoBIä½¿ç”¨æ ¸å¿ƒ¾pÈ»Ÿä¸ŽåŠŸèƒ½æ¨¡å—集æˆçš„æž¶æž„åQŒè¿™æ ·åœ¨¼‹®ä¿òq›_°½E›_®šæ€§ä¸Žå调性的基础上åˆä¿è¯äº†ç³»¾lŸå…·æœ‰å¾ˆå¼ºçš„æ‰©å±•能力。用æˆäh— 需使用SpagoBI的所有模å—,而是å¯ä»¥åªåˆ©ç”¨å…¶ä¸çš„一些模å—ã€?
SpagoBI使用了许多已有的开æºèÊYä»Óž¼Œå¦‚Spagoå’ŒSpagosi½{‰ã€‚å› æ¤ï¼ŒSpagoBI集æˆäº?Spago的特å¾å’ŒæŠ€æœ¯ç‰¹ç‚¹ï¼Œä½¿ç”¨å®ƒä»¬½Ž¡ç†å•†åŠ¡æ™ø™ƒ½å¯¹è±¡åQŒå¦‚报表ã€OLAP分æžã€äÈA表盘ã€è®°åˆ†å¡ä»¥åŠ(qi¨¢ng)æ•°æ®æŒ–掘模型½{‰ã€‚SpagoBI支æŒBI¾pÈ»Ÿçš„监控管ç†ï¼ŒåŒ…æ‹¬å•†åŠ¡æ™ø™ƒ½å¯¹è±¡çš„æŽ§åˆ¶ã€æ ¡éªŒã€è®¤è¯å’Œåˆ†é…‹¹ç¨‹ã€‚SpagoBI采用Portalet技术将所有的BI对象å‘å¸ƒåˆ°ç»ˆç«¯ç”¨æˆøP¼Œå› æ¤BI对象ž®±å¯ä»¥é›†æˆåˆ°ä¸ºç‰¹å®šçš„ä¼ä¸šéœ€æ±‚而已¾l选择好的Portal¾pÈ»Ÿä¸åŽ»ã€?
3.Bee™å¹ç›®
该项目是一套支æŒå•†åŠ¡æ™ºèƒ½é¡¹ç›®å®žæ–½çš„å·¥å…·å¥—äšgåQŒåŒ…括ETL工具和OLAP æœåŠ¡å™¨ã€‚Beeçš„ETL工具使用åŸÞZºŽPerlçš„BEIåQŒé€šè¿‡ç•Œé¢æè¿°‹¹ç¨‹åQŒä»¥XML形弘q›è¡Œå˜å‚¨ã€‚用户必™åÕd¯¹è½¬æ¢˜q‡ç¨‹˜q›è¡Œ¾~–ç 。Beeçš„ROLAP æœåС噍ä¿è¯å¤šé€šSQL 生æˆå’Œå¼ºæœ‰åŠ›çš„é«˜é€Ÿç¼“å˜ç®¡ç?使用MySQLæ•°æ®åº“管ç†ç³»¾l?。ROLAPæœåŠ¡å™¨é€šè¿‡SOAPåº”ç”¨æŽ¥å£æä¾›ä¸°å¯Œçš„å®¢æˆ·åº”ç”¨ã€‚Web Portalä½œäØ“(f¨´)主è¦çš„用æˆähŽ¥å£ï¼Œé€šè¿‡Web‹¹è§ˆå™¨è¿›è¡ŒæŠ¥è¡¨è®¾è®¡ã€å±•½Cºå’Œ½Ž¡ç†æŽ§åˆ¶åQŒåˆ†æžç»“æžœå¯ä»¥ä»¥Excelã€PDFã€PNGã€PowerPointã€?textå’ŒXML½{‰å¤š¿UåÅžå¼å¯¼å‡ºã€?
Bee™å¹ç›®çš„特点在于:(x¨¬)
â—?½Ž€å•å¿«æïL(f¨¥ng)š„æ•°æ®è®‰K—®åQ?
â—?支æŒé¢„先定义报表和实时查询;
â—?通过拖拽方å¼è½ÀL¾å®žçŽ°æŠ¥è¡¨å®šåˆ¶åQ?
â—?å®Œæ•´æŠ¥è¡¨çš„è½»æ¾æŽ§åˆÓž¼›
â—?以表和图˜q›è¡Œé«˜è´¨é‡çš„æ•°æ®å±•示ã€?/p>