Add a simple rule engine to your Spring-based applications

Use Spring built-in facilities to capture business logic

Any nontrivial software project contains a nontrivial amount of so-called business logic. What exactly constitutes business logic is debatable. In the mountains of code produced for a typical software application, bits and pieces here and there actually do the job the software was called for—process orders, control weapons systems, draw pictures, etc. Those bits contrast sharply with others that deal with persistence, logging, transactions, language oddities, framework quirks, and other tidbits of a modern enterprise application.

More often than not, the business logic is deeply intermixed with all those other pieces. When heavy, intrusive frameworks (such as Enterprise JavaBeans) are used, discerning where the business logic ends and framework-inspired code begins becomes especially difficult.

There is one software requirement rarely spelled out in the requirement definition documents yet has the power to make or break any software project: adaptability, the measure of how easy it is to change the software in response to business environment changes.

Modern companies are forced to be quick and flexible, and they want the same from their enterprise software. Business rules that were so painstakingly implemented in your classes' business logic today will become obsolete tomorrow and will need to be changed quickly and accurately. When your code has business logic buried deep inside tons and tons of those other bits, modification will quickly become slow, painful, and error-prone.

No wonder some of the trendiest fields in enterprise software today are rule engines and various business-process-management (BPM) systems. Once you look through the marketing-speak, those tools promise essentially the same thing: the Holy Grail of Business Logic captured in a repository, cleanly separated and existing by itself, ready to be called from any application you may have in your software house.

Though commercial rule engines and BPM systems have many advantages, they also include many shortcomings. The easy one to pick on is the price, which can sometimes easily reach into the seven digits. Another is the lack of practical standardization that continues today in spite of major industry efforts and multiple on-paper standards available. And, as more and more software shops adapt agile, lean, and quick development methodologies, those heavyweight tools find it difficult to fit in.

In this article, we build a simple rule engine that, on one hand, leverages the clear separation of business logic typical for such systems and, on the other hand—because it's piggy-backed on the popular and powerful J2EE framework—doesn't suffer from the complexity and "uncoolness" of the commercial offerings.

Spring time in the J2EE universe

After the complexity of enterprise software became unbearable and the business-logic problem entered the spotlight, the Spring Framework and others like it were born. Arguably, Spring is the best thing that happened to enterprise Java in a long time. Spring provides the long list of tools and small code conveniences that make J2EE programming more object-oriented, much easier, and, well, more fun.

In the heart of Spring lies the principle of Inversion of Control. This is a fancy and overloaded name, but it comes down to these simple ideas:

  • Your code's functionality is broken into small manageable pieces
  • Those pieces are represented by simple, standard Java beans (simple Java classes that exhibit some, but not all, of the JavaBeans specification)
  • You do not get involved with managing those beans (creating, destroying, setting dependencies)
  • Instead, the Spring container does it for you based on some context definition usually provided in the form of an XML file

Spring also provides many other features, such as a complete and powerful Model-View-Controller framework for Web applications, convenience wrappers for Java Database Connectivity programming, and a dozen other frameworks. But those subjects reach well outside this article's scope.

Before I describe what it takes to create a simple rule engine for Spring-based applications, let's consider why this approach is a good idea.

Rule-engine designs have two interesting properties that make them worthwhile:

  • Firstly, they separate the business logic code from other areas of the application
  • Secondly, they are externally configurable, meaning that the definitions of the business rules and how and in which order they fire are stored externally to the application and manipulated by the rule creator, not the application user or even a programmer

Spring provides a good fit for a rule engine. The highly componentized design of a properly-coded Spring application promotes the placing of your code into small, manageable, separate pieces (beans), which are externally configurable via the Spring context definitions.

Read on to explore this good match between what a rule-engine design needs and what the Spring design already provides.

The design of a Spring-based rule engine

We base our design on the interaction of Spring-controlled Java beans, which we call rule engine components. Let's define the two types of components we might need:

  • An action is a component that actually does something useful in our application logic
  • A rule is a component that makes a decision in a logical flow of actions

As we are big fans of good object-oriented design, the following base class captures the base functionality of all of our components to come, namely, the ability to be called by other components with some argument:

 public abstract class AbstractComponent {
   public abstract void execute(Object arg) throws Exception; 
}

Naturally the base class is abstract because we will never need one by itself.

And now, code for an AbstractAction, to be extended by other future concrete actions:

 

public abstract class AbstractAction extends AbstractComponent {

private AbstractComponent nextStep; public void execute(Object arg) throws Exception { this.doExecute(arg); if(nextStep != null) nextStep.execute(arg); } protected abstract void doExecute(Object arg) throws Exception;

public void setNextStep(AbstractComponent nextStep) { this.nextStep = nextStep; }

public AbstractComponent getNextStep() { return nextStep; }

}

As you can see, AbstractAction does two things: It stores the definition of the next component to be invoked by our rule engine. And, in its execute() method, it calls a doExecute() method to be defined by a concrete subclass. After doExecute() returns, the next component is invoked if there is one.

Our AbstractRule is similarly simple:

 

public abstract class AbstractRule extends AbstractComponent {

private AbstractComponent positiveOutcomeStep; private AbstractComponent negativeOutcomeStep; public void execute(Object arg) throws Exception { boolean outcome = makeDecision(arg); if(outcome) positiveOutcomeStep.execute(arg); else negativeOutcomeStep.execute(arg);

}

protected abstract boolean makeDecision(Object arg) throws Exception;

// Getters and setters for positiveOutcomeStep and negativeOutcomeStep are omitted for brevity

In its execute() method, the AbstractAction calls the makeDecision() method, which a subclass implements, and then, depending on that method's result, calls one of the components defined as either a positive or negative outcome.

Our design is complete when we introduce this SpringRuleEngine class:

 public class SpringRuleEngine {
   
   private AbstractComponent firstStep;
   
   public void setFirstStep(AbstractComponent firstStep) {
      this.firstStep = firstStep;
   }
   
   public void processRequest(Object arg) throws Exception {
      firstStep.execute(arg);
   }
   
}

That's all there is to our rule engine's main class: the definition of a first component in our business logic and the method to start processing.

But wait, where is the plumbing that wires all our classes together so they can work? You will next see how the magic of Spring helps us with that task.

Spring-based rule engine in action

Let's look at a concrete example of how this framework might work. Consider this use case: we must develop an application responsible for processing loan applications. We need to satisfy the following requirements:

  • We check the application for completeness and reject it otherwise
  • We check if the application came from an applicant living in a state where we are authorized to do business
  • We check if applicant's monthly income and his/her monthly expenses fit into a ratio we feel comfortable with
  • Incoming applications are stored in a database via a persistence service that we know nothing about, except for its interface (perhaps its development was outsourced to India)
  • Business rules are subject to change, which is why a rule-engine design is required

First, let's design a class representing our loan application:

 

public class LoanApplication { public static final String INVALID_STATE = "Sorry we are not doing business in your state"; public static final String INVALID_INCOME_EXPENSE_RATIO = "Sorry we cannot provide the loan given this expense/income ratio"; public static final String APPROVED = "Your application has been approved"; public static final String INSUFFICIENT_DATA = "You did not provide enough information on your application"; public static final String INPROGRESS = "in progress"; public static final String[] STATUSES = new String[] { INSUFFICIENT_DATA, INVALID_INCOME_EXPENSE_RATIO, INVALID_STATE, APPROVED, INPROGRESS };

private String firstName; private String lastName; private double income; private double expences; private String stateCode; private String status; public void setStatus(String status) { if(!Arrays.asList(STATUSES).contains(status)) throw new IllegalArgumentException("invalid status:" + status); this.status = status; }

// Bunch of other getters and setters are omitted

}

Our given persistence service is described by the following interface:

 public interface LoanApplicationPersistenceInterface {
   public void recordApproval(LoanApplication application) throws Exception;
   public void recordRejection(LoanApplication application) throws Exception;
   public void recordIncomplete(LoanApplication application) throws Exception;
}

We quickly mock this interface by developing a MockLoanApplicationPersistence class that does nothing but satisfy the contract defined by the interface.

We use the following subclass of the SpringRuleEngine class to load the Spring context from an XML file and actually begin the processing:

 public class LoanProcessRuleEngine extends SpringRuleEngine {
   public static final SpringRuleEngine getEngine(String name) {
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("SpringRuleEngineContext.xml");
      return (SpringRuleEngine) context.getBean(name);
   }
}

At this moment, we have the skeleton in place, so it is the perfect time to write a JUnit test, which appears below. A few assumptions are made: We expect our company to operate in only two states, Texas and Michigan. And we only accept loans with an expense/income ratio of 70 percent or better.

 

public class SpringRuleEngineTest extends TestCase {

public void testSuccessfulFlow() throws Exception { SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor"); LoanApplication application = new LoanApplication(); application.setFirstName("John"); application.setLastName("Doe"); application.setStateCode("TX"); application.setExpences(4500); application.setIncome(7000); engine.processRequest(application); assertEquals(LoanApplication.APPROVED, application.getStatus()); } public void testInvalidState() throws Exception { SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor"); LoanApplication application = new LoanApplication(); application.setFirstName("John"); application.setLastName("Doe"); application.setStateCode("OK"); application.setExpences(4500); application.setIncome(7000); engine.processRequest(application); assertEquals(LoanApplication.INVALID_STATE, application.getStatus()); } public void testInvalidRatio() throws Exception { SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor"); LoanApplication application = new LoanApplication(); application.setFirstName("John"); application.setLastName("Doe"); application.setStateCode("MI"); application.setIncome(7000); application.setExpences(0.80 * 7000); //too high engine.processRequest(application); assertEquals(LoanApplication.INVALID_INCOME_EXPENSE_RATIO, application.getStatus()); } public void testIncompleteApplication() throws Exception { SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor"); LoanApplication application = new LoanApplication(); engine.processRequest(application); assertEquals(LoanApplication.INSUFFICIENT_DATA, application.getStatus()); }

Obviously, the unit tests fail spectacularly at the moment because we have not implemented any logic yet. However, as we move along, more and more tests will pass, and JUnit will eventually reward us with that ever-satisfying green bar.

Let's continue to the actual implementation of our actions and rules. We expect our actions to interact with our persistence service so we have a common base class for them:

 public abstract class AbstractPersistenceAwareAction extends AbstractAction {
   private LoanApplicationPersistenceInterface persistenceService;
   public void setPersistenceService(LoanApplicationPersistenceInterface persistenceService) {
      this.persistenceService = persistenceService;
   }
   public LoanApplicationPersistenceInterface getPersistenceService() {
      return persistenceService;
   }
}

The first business rule in our process requires the loan application to be complete:

 public class ValidApplicationRule extends AbstractRule {
   protected boolean makeDecision(Object arg) throws Exception {
      LoanApplication application = (LoanApplication) arg;
      if(application.getExpences() == 0 ||
            application.getFirstName() == null ||
            application.getIncome() == 0 ||
            application.getLastName() == null ||
            application.getStateCode() == null) {
         application.setStatus(LoanApplication.INSUFFICIENT_DATA);
         return false;
      }
      return true;
   }
}

Note some interesting facts about this class: It is completely self-contained. It can be instantiated by itself or outside any kind of application container, and the logic inside the class can be independently developed and unit-tested. These facts make the class a perfect building block for a rule-based application.

Now it is time to finally hook things up into the Spring container. As you can see in a unit test, we use a LoanProcessRuleEngine class as an entry point to our rule-engine requesting bean under the name of SharkysExpressLoansApplicationProcessor. Here is how this bean is defined in SpringRuleEngineContext.xml:

  <!-- rule engine processor -->
   <bean id="SharkysExpressLoansApplicationProcessor" class="SpringRuleEngine">
      <property name="firstStep">
         <ref bean="ValidApplicationRule"/>
      </property>
   </bean>

The bean simply designates the ValidApplicationRule component to be the first step in our business process. The component itself is defined below:

  <!-- validation -->
   <bean id="ValidApplicationRule" class="ValidApplicationRule">
      <property name="positiveOutcomeStep">
         <ref bean="ValidStateRule"/>
      </property>
      <property name="negativeOutcomeStep">
         <ref bean="RejectionAction"></ref>
      </property>
   </bean>

As you can see, the rule itself is defined right here in the Spring context: if the loan application is valid, the application is checked for the correct state; if it is not valid, control passes to the RejectionAction.

The RejectionAction is simple:

 public class ProcessRejectionAction extends AbstractPersistenceAwareAction {
   protected void doExecute(Object arg) throws Exception {
      LoanApplication application = (LoanApplication) arg;
      if(LoanApplication.INSUFFICIENT_DATA.equals(application.getStatus()))
         this.getPersistenceService().recordIncomplete(application);
      else
         this.getPersistenceService().recordRejection(application);
   }
}

It is defined in the Spring context as follows (note the reference to the persistence service mocked up by a dummy class):

 

<!-- rejection --> <bean id="RejectionAction" class="ProcessRejectionAction"> <property name="persistenceService"> <ref bean="LoanApplicationPersistenceService"/> </property>

</bean> <!-- persistence service --> <bean id="LoanApplicationPersistenceService" class="MockLoanApplicationPersistence"/>

Our next business rule checks if the loan application originated from the valid state:

 

public class ValidStateRule extends AbstractRule {

private List validStates; protected boolean makeDecision(Object arg) throws Exception { LoanApplication application = (LoanApplication) arg; if(validStates.contains(application.getStateCode())) { return true; } application.setStatus(LoanApplication.INVALID_STATE); return false; }

public void setValidStates(List validStates) { this.validStates = validStates; } }

Interestingly enough, our code does not know which states constitute the list of valid states. This is business information and, as such, belongs to the context definition:

  <!-- check valid state -->
   <bean id="ValidStateRule" class="ValidStateRule">
      <property name="validStates">
         <list>
            <value>TX</value>
            <value>MI</value>
         </list>
      </property>
      <property name="positiveOutcomeStep">
         <ref bean="ValidIncomeExpenseRatioRule"/>
      </property>
      <property name="negativeOutcomeStep">
         <ref bean="RejectionAction"></ref>
      </property>
   </bean>

Once again, Spring's built-in facilities allow us to extract both the logic flow and parameter data out of the code and configure it externally.

As you can see, the Spring application container provides all the necessary wiring for our classes. At startup, Spring creates all the necessary objects and sets the object dependencies between them. At runtime, our business rules are executed with logic and parameters defined in the Spring context file, not in our code. The rest of the loan application example is configured and developed in a similar fashion. The complete source code and matching Spring configuration is available from Resources.

Conclusion

In this article, I showed a few of the many ways in which Spring can help you develop a rule-based application. You could also use the built-in AOP (aspect-oriented programming) support to "mix-in" logging or transaction code into your rules and actions without polluting your precious business logic.

Spring application contexts are potentially reloadable. An application could change the business rules and parameters (by modifying context XML) and reload the context on the fly. Imagine a GUI application that does just that. Such a setup would offer capabilities similar to ones offered by multimillion-dollar commercial rule-engine systems.

Hopefully this article can inspire you to try some new and exciting approaches and make your coding efforts more successful in the future.

Mikhail Garber is a Dallas-based independent technology consultant with more than 12 years of experience in enterprise software development. Garber specializes in Java/J2EE, databases, messaging, and open source solutions. His services have been employed by such organizations as Mary Kay Cosmetics, Boeing Defense and Space, Verizon Wireless, the US government, Lockheed Martin, and many others.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies