hengheng123456789

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  297 Posts :: 68 Stories :: 144 Comments :: 0 Trackbacks

Using Drools in Your Enterprise Java Application

by Paul Browne
08/24/2005

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.

Frameworks All the Way Down

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
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.

When Should I Use a Rule Engine?


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:

  • How complex is my application? For applications that shuffle data to and from a database, but not much more, it is probably best not to use a rule engine. However, where there is even a moderate amount of processing implemented in Java, it is worthwhile to consider the use of Drools. This is because most applications develop complexity over time, and Drools will let you cope easily with this.
  • What is the lifetime of my application? The answer to this is often "surprisingly long"--remember the mainframe programmers who thought their applications wouldn't be around for the year 2000? Using a rule engine pays off, especially in the medium to long term. As this article demonstrates, even prototypes can benefit from the combination of Drools and agile methods to take the "prototype" into production.
  • Will my application need to change? The only sure thing about your requirements is that they will change, either during or just after development. Drools helps you cope with this by specifying the business rule in one or more easy-to-configure XML files.

What About Performance?

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:

  • Avoids badly written code: Drools guides developers to do "the right thing." You may be sure the code you are writing is good, but would you say the same for the code of your co-developers? Using a framework makes it easier to write good, fast code.
  • Optimized framework: How often have you seen business logic that repeatedly accesses a database for the same information, slowing down the entire application? Used correctly, Drools can remember not only the information, but also the results of previous tests using this information, giving the entire application a speed boost.
  • Rete algorithm: Many times we apply "if" conditions that we didn't really need. The Rete algorithm, as implemented by Drools, replaces all of the 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!


Where Were We?

In our previous article, we wrote a simple stock trading application based around the Drools engine. We implemented various business rules, showed how we could rapidly change the rules to meet changing business requirements, and wrote JUnit tests to give us a high degree of confidence that the system would act as it was supposed to. However, the application as we left it had little or no user interface, and used hard-coded data instead of a database. To evolve our application into something that is more enterprise level, we need to add two main things:
  • Some sort of user interface, ideally based one of the standard web-presentation frameworks.
  • A Data Access Object (DAO) to let Drools work with a database (or other back end system).

Calling the Rule Engine from a Presentation Framework

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.


Integrating the Rule Engine with the Database Layer

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:

  • Only the business layer should talk to the data layer; if a class in the presentation layer (front end) wants some data, it should pass through the business layer first. This helps makes our code easier to organize and read.
  • As far as possible, we should keep our data layer stateless--we should hold client data elsewhere (e.g., in the server session at the web front end, as per the previous example). This is distinct from caching of data, which we can do at this level. The difference between the two is state information is often user-specific, while data we cache at the data access layer is mainly sharable across the application. Organizing our layer in this way increases performance.
  • We should allow the business logic to decide if data is needed or not--if not needed, the call to get the data should not be made.

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:

  • At the top of the file, we have several new <java:import> statements to reference the StockNameDao, DaoImplementation, and DaoFactory classes that we added to the system.
  • We have a new tag, <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.
  • The 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.

Summary

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.

Resources

Paul Browne , based in Dublin, Ireland, has been consulting in enterprise Java with FirstPartners.net for almost seven years.

posted on 2007-09-03 17:28 哼哼 阅读(1492) 评论(0)  编辑  收藏 所属分类: JAVA-BIJAVA-Web

只有注册用户登录后才能发表评论。


网站导航: