Jess Rule Engine und JSR 94

1. Übersicht

Die Verwendung einer Regelengine ist eine hervorragende Möglichkeit, die Geschäftslogik von unserem Code zu trennen und unseren Anwendungscode vor geschäftlichen Änderungen zu schützen.

In einem früheren Artikel zu Java Rule Engines haben wir die JSR 94-Spezifikation erwähnt. Die Jess Rule Engine ist als Implementierung des Referenzregel-Treibers für JSR 94 von besonderer Bedeutung. Schauen wir uns das an.

2. Jess Rule Engine

Jess ist eine der frühesten Regel-Engines, die sich problemlos in Java integrieren lassen. Jess verwendet eine verbesserte Implementierung des hocheffizienten Rete-Algorithmus, wodurch er in den meisten Szenarien viel schneller als eine einfache Java-Schleife ist.

Regeln können aus Regelsätzen ausgeführt werden, die in der nativen Jess Rules Language, einer erweiterten Lisp-basierten Syntax, oder aus einem ausführlicheren XML-Format geschrieben sind. Wir werden das native Format verwenden.

Es gibt eine Eclipse-basierte IDE für die Entwicklung (für ältere Versionen von Eclipse) und eine hervorragende Dokumentation zur Verwendung und Integration von Jess in Java. Es gibt sogar eine REPL-Befehlszeilenschnittstelle, über die wir unsere Ideen ausprobieren können, bevor wir eine Regeldatei erstellen.

Als Referenzregel-Engine für JSR 94 ist Jess per Definition JSR 94-kompatibel, obwohl es sich nicht mehr in der aktiven Entwicklung befindet.

2.1. Ein kurzes Wort zu JSR 94

JSR 94 bietet eine API, mit der wir unabhängig von der von uns gewählten Regelengine sind. Wir können jede JSR 94-kompatible Regelengine in unseren Code einfügen und einige Regeln ausführen, ohne die Art und Weise ändern zu müssen, wie wir mit der Regelengine in unserer Anwendung interagieren.

Dies bedeutet nicht, dass die zugrunde liegenden Regeln der Regelengine gleich aussehen. Möglicherweise müssen wir diese neu schreiben, wenn wir die Regelengine ändern. Es bedeutet jedoch, dass wir keine Teile unserer Anwendung neu schreiben müssen, um die neue Regelengine zu verwenden. Die einzigen Codeänderungen, die wir benötigen, sind das Aktualisieren des Treibernamens und einiger Regeldateinamen.

2.2. Der Jess JSR 94 Fahrer

Obwohl für JSR 94 ein Referenzregel-Engine- Treiber für Jess enthalten ist, ist Jess selbst nicht enthalten, da es sich um ein lizenziertes kommerzielles Produkt handelt. Der Referenztreiber befindet sich im Paket org.jcp.jsr94.jess , aber ein neuerer Treiber ist im Paket jess.jsr94 verfügbar , wenn wir Jess herunterladen.

Schauen wir uns zunächst Jess 'native Java-Integration an, bevor wir uns ansehen, wie die JSR 94-Schicht dies ändert.

3. Bereitgestellte Beispiele

Bevor wir Jess in unseren Code integrieren, stellen wir sicher, dass wir ihn heruntergeladen und auf unserem Klassenpfad verfügbar gemacht haben. Wir müssen uns für den kostenlosen 30-Tage-Testdownload registrieren, es sei denn, wir haben bereits eine Lizenz.

Laden Sie also Jess herunter, entpacken Sie die heruntergeladene Version Jess71p2.jar und führen Sie eines der Beispiele aus, um sicherzustellen, dass wir eine funktionierende Version haben.

3.1. Standalone Jess

Schauen wir uns das Verzeichnis Jess71p2 / examples an , in dem das Verzeichnis jess einige Beispielregelsätze enthält. Das Verzeichnis price_engine zeigt eine Integration, die über ein ant build.xml- Skript ausgeführt werden kann . Lassen Sie uns unser Verzeichnis in das Beispiel der Pricing Engine ändern und das Programm über ant test ausführen :

cd Jess71p2/examples/pricing_engine ant test

Dadurch wird ein Beispiel für einen Preisregelsatz erstellt und ausgeführt:

Buildfile: Jess71p2\examples\pricing_engine\build.xml ... test: [java] Items for order 123: [java] 1 CD Writer: 199.99 ... [java] Items for order 666: [java] 1 Incredibles DVD: 29.99 [java] Offers for order 666: [java] BUILD SUCCESSFUL Total time: 1 second

3.2. Jess Mit JSR 94

Nachdem Jess funktioniert hat, laden wir JSR 94 herunter und entpacken es, um ein jsr94-1.0-Verzeichnis mit den Verzeichnissen ant, doc, lib und src zu erstellen.

unzip jreng-1_0a-fr-spec-api.zip

Dies gibt uns die JSR 94-API und den Jess-Referenztreiber, wird jedoch nicht mit der lizenzierten Jess-Implementierung geliefert. Wenn wir also jetzt versuchen, ein Beispiel auszuführen, wird der folgende Fehler angezeigt:

Error: The reference implementation Jess could not be found.

Fügen wir also die Jess-Referenzimplementierung jess.jar hinzu , die Teil des zuvor heruntergeladenen Jess71p2 war, kopieren Sie sie in das JSR 94 lib-Verzeichnis und führen Sie das folgende Beispiel aus:

cp Jess71p2/lib/jess.jar jsr94-1.0/lib/ java -jar jsr94-1.0/lib/jsr94-example.jar

In diesem Beispiel werden einige Regeln ausgeführt, um das verbleibende Guthaben eines Kunden zu ermitteln, wenn Rechnungen bezahlt werden:

Administration API Acquired RuleAdministrator: [email protected] ... Runtime API Acquired RuleRuntime: [email protected] Customer credit limit result: 3000 ... Invoice 2 amount: 1750 status: paid Released Stateful Rule Session.

4. Jess in Java integrieren

Nachdem wir Jess und JSR 94 heruntergeladen und einige Regeln sowohl nativ als auch über JSR ausgeführt haben, schauen wir uns an, wie ein Jess-Regelsatz in ein Java-Programm integriert wird.

In our example, we'll start by executing a simple Jess rules file, hellojess.clp, from Java code, and then look at another rules file, bonus.clp, that will use and modify some of our objects.

4.1. Maven Dependency

There's no Maven dependency available for Jess, so if we haven't already done so, let's download and unpack the Jess jar (jess.jar) and mvn install it to our local Maven repository:

mvn install:install-file -Dfile=jess.jar -DgroupId=gov.sandia -DartifactId=jess -Dversion=7.1p2 -Dpackaging=jar -DgeneratePom=true

We can then add it as a dependency in the usual way:

 gov.sandia jess 7.1p2 

4.2. Hello Jess Rules File

Next, let's create the simplest of rules files to print out a message. We'll save the rules file as hellojess.clp:

(printout t "Hello from Jess!" crlf)

4.3. Jess Rule Engine

Now, let's create an instance of the Jess Rete rule engine, reset() it to its initial state, load up the rules in hellojess.clp, and run them:

public class HelloJess { public static void main(String[] args) throws JessException { Rete engine = new Rete(); engine.reset(); engine.batch("hellojess.clp"); engine.run(); }

For this simple example, we've just added the potential JessException to our main method's throws clause.

When we run our program, we'll see the output:

Hello from Jess!

5. Integrating Jess to Java With Data

Now that everything is installed correctly and we can run rules, let's see how we add data for the rule engine to process and how we retrieve the results.

First, we'll need some Java classes to work with, and then a new ruleset that uses them.

5.1. Model

Let's create some simple Question and Answer classes:

public class Question { private String question; private int balance;  // getters and setters  public Question(String question, int balance) { this.question = question; this.balance = balance; } } public class Answer { private String answer; private int newBalance;  // getters and setters  public Answer(String answer, int newBalance) { this.answer = answer; this.newBalance = newBalance; } }

5.2 Jess Rule With Input and Output

Now, let's create a simple Jess ruleset called bonus.clp that we'll pass a Question to and receive an Answer from.

First, we import our Question and Answer classes and then use Jess's deftemplate function to make them available to the rule engine:

(import com.baeldung.rules.jsr94.jess.model.*) (deftemplate Question (declare (from-class Question))) (deftemplate Answer (declare (from-class Answer)))

Note the use of parentheses, which denote Jess function calls.

Now, let's use defrule to add a single rule avoid-overdraft in Jess's extended Lisp format that gives us a bonus of $50 if the balance in our Question is below zero:

(defrule avoid-overdraft "Give $50 to anyone overdrawn" ?q <- (Question { balance  (add (new Answer "Overdrawn bonus" (+ ?q.balance 50))))

Here, the “?” binds an object to a variable q when the conditions on the right-hand side of the “<-“ match. In this case, that's when the rule engine finds a Question that has a balance less than 0.

When it does, then the actions to the right of the “=>” are triggered so the engine adds a new Answer object to the working memory. We give it the two required constructor arguments: “Overdrawn bonus” for the answer parameter and a (+) function to calculate the newAmount parameter.

5.3. Manipulating Data With the Jess Rule Engine

We can use add() to add a single object at a time to our rule engine's working memory, or addAll() to add a collection of data. Let's use add() to add a single question:

Question question = new Question("Can I have a bonus?", -5); engine.add(data);

With all of our data in place, let's execute our rules:

engine.run();

The Jess Rete engine will work its magic and return when all relevant rules have executed. In our case, we'll have an Answer to inspect.

Let's use a jess.Filter to extract our Answer from the rule engine into an Iterable results object:

Iterator results = engine.getObjects(new jess.Filter.ByClass(Answer.class)); while (results.hasNext()) { Answer answer = (Answer) results.next(); // process our Answer }

We don't have any reference data in our simple example, but when we do, we can use a WorkingMemoryMarker and engine.mark() to mark the state of the rule engine's working memory after adding the data. Then we can call engine.resetToMark with our marker to reset the working memory to our “loaded” state and efficiently reuse the rule engine for a different set of objects:

WorkingMemoryMarker marker; // load reference data marker = engine.mark(); // load specific data and run rules engine.resetToMark(marker);

Now, let's take a look at how we run this same ruleset using JSR 94.

6. Using JSR 94 to Integrate the Jess Rule Engine

JSR 94 standardizes how our code interacts with a rule engine. This makes it easier to change our rule engine without significantly changing our application if a better alternative comes along.

The JSR 94 API comes in two main packages:

  • javax.rules.admin – for loading drivers and rules
  • javax.rules – to run the rules and extract results

We'll look at how to use the classes in both of these.

6.1. Maven Dependency

First, let's add a Maven dependency for jsr94:

 jsr94 jsr94 1.1 

6.2. Administration API

To start using JSR 94, we need to instantiate a RuleServiceProvider. Let's create one, passing it our Jess rules driver:

String RULE_SERVICE_PROVIDER="jess.jsr94"; Class.forName(RULE_SERVICE_PROVIDER + ".RuleServiceProviderImpl"); RuleServiceProvider ruleServiceProvider = RuleServiceProviderManager.getRuleServiceProvider(RULE_SERVICE_PROVIDER);

Now, let's get Jess's JSR 94 RuleAdministrator, load our example ruleset into a JSR 94 RuleExecutionSet, and register it for execution with a URI of our choice:

RuleAdministrator ruleAdministrator = serviceProvider.getRuleAdministrator(); InputStream ruleInput = JessRunner.class.getResourceAsStream(rulesFile); HashMap vendorProperties = new HashMap(); RuleExecutionSet ruleExecutionSet = ruleAdministrator .getLocalRuleExecutionSetProvider(vendorProperties) .createRuleExecutionSet(ruleInput, vendorProperties); String rulesURI = "rules://com/baeldung/rules/bonus"; ruleAdministrator.registerRuleExecutionSet(rulesURI, ruleExecutionSet, vendorProperties);

The Jess driver doesn't need the vendorProperties map we supplied to RuleAdministrator, but it's part of the interface, and other vendors may require it.

Now that our rule engine provider, Jess, has been initialized and our ruleset has been registered, we are almost ready to run our rules.

Before we can run them, we need a runtime instance and a session to run them in. Let's also add a placeholder, calculateResults(), for where the magic will happen, and release the session:

RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime(); StatelessRuleSession statelessRuleSession = (StatelessRuleSession) ruleRuntime.createRuleSession(rulesURI, new HashMap(), RuleRuntime.STATELESS_SESSION_TYPE); calculateResults(statelessRuleSession); statelessRuleSession.release();

6.3. Execution API

Now that we have everything in place, let's implement calculateResults to supply our initial data, execute our rules in a stateless session, and extract the results:

List data = new ArrayList(); data.add(new Question("Can I have a bonus?", -5)); List results = statelessRuleSession.executeRules(data);

Since JSR 94 was written before JDK 5 came along, the API doesn't use generics so let's just use an Iterator to see the results:

Iterator itr = results.iterator(); while (itr.hasNext()) { Object obj = itr.next(); if (obj instanceof Answer) { int answerBalance = ((Answer) obj).getCalculatedBalance()); } }

We've used a stateless session in our example, but we can also create a StatefuleRuleSession if we want to maintain state between invocations.

7. Conclusion

In this article, we learned how to integrate the Jess rule engine into our application by using Jess's native classes and, with a bit more effort, by using JSR 94. We've seen how business rules can be separated into separate files that get processed by the rule engine when our application runs.

Wenn wir Regeln für dieselbe Geschäftslogik haben, die für eine andere JSR 94-kompatible Regelengine geschrieben wurden, können wir einfach den Treiber für unsere alternative Regelengine hinzufügen und den Treibernamen aktualisieren, den unsere Anwendung verwenden soll, und es sollten keine weiteren Codeänderungen vorgenommen werden notwendig.

Unter jess.sandia.gov finden Sie weitere Informationen zum Einbetten von Jess in eine Java-Anwendung. Oracle bietet eine nützliche Anleitung für den Einstieg in die Java Rule Engine-API (JSR 94).

Wie üblich ist der Code, den wir uns in diesem Artikel angesehen haben, auf GitHub verfügbar.