Knowledge-based systems have been a well-known research topic in AI for decades. Their presence in corporate applications, however, has been remarkably small. A number of reasons may explain this fact, but it looks like there is a main one: knowledge-based systems are impractical because they depend on huge, pre-built domain knowledge bases to work properly. At least, this is what corporate developers and managers seem to think.
By submitting your email address, you agree to receive emails regarding relevant topic offers from TechTarget and its partners. You can withdraw your consent at any time. Contact TechTarget at 275 Grove Street, Newton, MA.
It is possible, however, to build useful knowledge-based systems without an exhaustive, explicit domain description. The so-called production rules, or condition-action rules, can be seen as an incremental, implicit approach to domain description. And condition-action rules, as you may have already noticed, are structurally identical to business rules.
In fact, business rules may be thought of as small pieces of knowledge about a business domain. Problem is, they have always been scattered by different applications, often replicated and usually hard-coded. As a consequence, they end up being neither maintainable nor reusable.
The use of parametric, encapsulated business rules has already been recognized as a fundamental need in corporate application development. In response to such a need, some AI-oriented products have been morphing into Java rule engines, and some application server products have already shifted their development focus to declarative rules. So it seems appropriate to discuss a design pattern that may foster a broader use of encapsulated business rules.
For this to be true, the pattern must not be dependent on specific and often proprietary implementations. It must be simple but still leave room for further refinement. It must also adapt to distributed environments. Particularly, it should be easy to implement on the J2EE platform as it is the preferred choice for developing distributed corporate applications today.
Being so, this article starts by discussing a proto-pattern called Encapsulated Business Rules. For the sake of simplicity, the pattern will be described in terms of plain Java classes and interfaces. Then, in the Consequences and Related Ideas section a possible implementation using Message-Driven Enterprise JavaBeans will be discussed.
Encapsulated Business Rules Pattern
You are developing a typical e-commerce application where business rules are used to assign each user to a category. For example, you may want to place users in one of two categories, those who deserve credit and those who do not. Or, you may want to find out how much credit should be granted to a user (within a US$ 2,500-25,000 range, say) according to his annual income.
This is just one among many other possible scenarios. Whatever the scenario, the goal is always the same: to apply the appropriate business rules to the information given by the user and the business analysts (the initial problem state) until some valid result (the final problem state) is reached.
You want your business rules to follow a standard format so they can be:
- Easily modified, extended and reused.
- Easily (and maybe dynamically) added to the rule set of possibly many distributed components.
- Executed in response to the current problem state; in other words, they should behave opportunistically and be applied whenever the right conditions arise.
- Developers are used to implementing business rules as methods that usually end up being scattered by various classes; this clearly hampers comprehensibility, maintainability and reusability.
- As a highly undesirable side effect, such an approach discourages the participation of business analysts in rule design.
- At the other end of the spectrum, there are rule engines that can be comfortably used by business analysts.
- Rule engines, however, are proprietary, quite expensive products; their learning curve can be steep and the resulting cost/benefit will often limit their use to large-scale projects.
Build your business rules as condition-action rules. Basically, they will be made up of two main methods:
- isConditionTrue() applies any number of tests against the problem state in order to find out whether the rule should fire.
- If the above method returns true then doAction() executes.
The problem state upon which business rules will operate can be seen as a simple Facade  in front of the domain model. Depending on the complexity of the domain, the problem state may have attributes which are objects themselves (particularly immutable ones). The value of each attribute is usually derived from domain objects or retrieved from the database. In any case, only public accessors to the problem state private attributes are exposed to clients.
Let's take a closer look at the implementation of such business rules. First, all rules must either:
- Extend an abstract class should some common or default behavior exist among them.
- Implement an interface otherwise.
A typical implementation would look like the following:
Let's now consider a simple concrete rule where the annual income of an user is evaluated and a score is set according to the evaluation:
The problem state being used by such a rule would be something like:
Rules like the one shown above can then be instantiated and added to the rule set of an evaluator. Since evaluators should react to changes in the problem state, they will be set up as observers (or listeners). As such, they will be notified whenever interesting changes occur in the problem state they are observing. Notifications, or callbacks, are asynchronous messages sent by the observable to its observers.
This is the classical Observer pattern , easily implemented in Java by means of the java.util.Observer interface and the java.util.Observable class: the problem state would extend Observable and the abstract evaluator would implement the Observer interface (see Figure 1).
Now, evaluators should have a chance to apply their rules whenever they are notified. Such an opportunistic behavior is feasible only when evaluators are running concurrently as active objects. This is the reason why the abstract evaluator also implements the Runnable interface. A simple critical section will protect the problem state against concurrent updates.
Figure 1. Proto-pattern with local active objects.
Concrete evaluators would just need to extend the abstract evaluator defined above and build their specific rule sets:
Those who really want to see how such an approach works in practice may download the example package. The output will be something like:
Even though the example might be over-simplified for pedagogical reasons, it demonstrates how encapsulated business rules can play an important role in e-commerce applications. The example also shows that it may not be that hard to turn a distributed component like an EJB into a rule-based component.
- Rule implementation is still left to programmers but business analysts can now fully participate in the design process.
- Rule encapsulation significantly enhances comprehensibility and maintainability.
- Rules can be formulated so as to take into account intra/inter-application reusability.
- Rules are built against an interface describing the problem state; should new information be added to the interface, existing concrete rules can be extended through inheritance or new ones can be formulated (the problem state and the business rule interfaces should both adhere to the Open-Closed Principle ).
Consequences and Related Ideas
The author has been successfully using encapsulated rules in her PhD project, which addresses a fairly complex engineering design problem. Distributed observers are simulated there by active objects. In a single observer, commercial Web-based application, the same architecture has been used to implement a simple financial simulator.
In a real distributed environment like J2EE, two main variants of the architecture described so far could be implemented. Their differences are discussed in the following sections.
Evaluators and problem state are local
If evaluators and problem state are in the same server, then the lightweight implementation discussed above can be encapsulated in a component (EvalEngine, for example) which would expose to clients a single method called evaluate(). Figure 2 is an overview of how such a component would typically interact with the underlying J2EE framework and the client.
Figure 2. Possible scenario when evaluators and problem state are local.
The sequence diagram shows that the Data Access Object ,  would copy the final problem state into a Transfer Object  (also known as Data Transfer Object ). The presentation tier could then use EvalTO to dynamically generate a result page.
In this scenario evaluators can still be active objects as they are not running inside the EJB container. Besides, when constrained to live in the same server they may be grouped according to any meaningful criteria. Such groups could then be assembled into a federated architecture that may fit some domains quite well.
Evaluators and problem state are distributed
To be more precise, evaluators may live in many different servers; the problem state (which is always a Singleton ) may live in one of those servers or elsewhere. In this scenario evaluators would be implemented as Message-Driven Beans (MDBs) for a number of reasons:
- MDBs are stateless in respect to the client; this matches the architecture described so far quite well since all state changes are kept in a single place, the problem state.
- They can respond to JMS messages just as observers do when hit by notifications.
- The client needs to know nothing about their implementation; in fact, the client just needs to pass them the problem state within a JMS message.
The easiest way to ensure the problem state will remain consistent through the evaluation process is to build it as an Entity Bean. Like an observable protected by a critical section, an Entity Bean can be safely shared because it is managed by the EJB container.
The decisions made so far lead to the model depicted in Figure 3. The component diagram shows the evaluators as MDBs and the problem state as an EB. Their interaction is mediated by a JMS Topic.
Figure 3. Component interaction when evaluators and problem state are distributed.
Such an implementation could be easily derived from the plain Java implementation described earlier. You should start by modifying the abstract Evaluator class so it now implements two interfaces, javax.ejb.MessageDrivenBean and javax.jms.MessageListener (Evaluator would not be an abstract class anymore as MDBs are not allowed to be abstract by the specification). Likewise, ProblemState now implements javax.ejb.EntityBean.
As a MessageDrivenBean, each evaluator would be responsible for registering itself as a subscriber to the StateHasChanged JMS Topic. All JMS-related initialization would take place within ejbCreate() (for those who want to know more, please see , for example).
As a MessageListener, each evaluator would need to provide an implementation for the onMessage() method. This method has a single parameter, a javax.jms.Message which would actually be an ObjectMessage. Since its getObject() method can return any Serializable object, the message could wrap either a Handle to the problem state or an Updatable Transfer Object .
If the Updatable Transfer Object strategy is adopted, the problem state will need to keep a version number. As soon as the problem state gets the first mutable Transfer Object from one of the evaluators, it updates itself, increments its version number and publishes the updated state to the JMS Topic. From now on, if the problem state gets any other Transfer Object whose version number is lesser than the current one, it will just be ignored. This ensures all evaluators will be applying their rules to the latest version of the problem state.
The sample code of the J2EE implementation will not be included here anymore because it involves too many implementation details. Those details end up obscuring rather than exposing the pattern structure.
Encapsulated business rules, being self-contained, are convenient micromodules: given an interface describing the problem state, you can build any number of rules that use such an interface in different ways. If rules are designed to take full advantage of abstract classes and inheritance, they can be the foundation of highly extensible applications.
When well-designed, encapsulated business rules are also independent, this means you can start with a small number of rules and then add new ones as needed. New rules should not impact existing ones; on the contrary, they mean more knowledge and, therefore, better chances of producing the best possible result.
Once implemented as an MDB, a rule-based component can provide flexible, sophisticated business services: just combine it with other domain-related MDBs and let them collaborate towards a common goal. MDBs make the ideal building blocks for loosely coupled applications.
A beneficial side-effect of the approach described here is that business rules can now be easily documented through standard javadoc automation. This further enhances their maintainability and reusability.
- E. Gamma, R. Helm, R. E. Johnson and J. Vlissides, Design Patterns, Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.
- example package with the source code of the plain Java implementation.
- B. Meyer, Object-Oriented Software Construction, 2nd edition. Prentice Hall PTR, 1997.
- D. Malks, D. Alur and J. Crupi, Core J2EE Patterns: Best Practices and Design Strategies, 2nd edition. Prentice Hall, 2003.
- F. Marinescu, EJB Design Patterns: Advanced Patterns, Processes and Idioms. John Wiley & Sons, 2000.
- A Message-Driven Bean Example
My thanks to Kevlin Henney and Gal Binyamini for their thoughtful comments on the structure and contents of this proto-pattern, and to Prof. Ralph Johnson for comparing it with other approaches. I would also like to thank the many people who have taken the time to send me their impressions on the first version of this article.
UML diagrams developed with Sparx Systems Enterprise Architect.
About the author
Cristina Belderrain is a software engineer and architect experienced in Java technology and object-oriented analysis and design. She holds a MSc degree in Engineering from Polythecnic School, University of Sao Paulo, Brazil, where she has also been pursuing a PhD degree. She is a Sun Certified Enterprise Architect for J2EE Technology.