Jess Rule Engine e JSR 94

1. Panoramica

L'utilizzo di un motore di regole è un ottimo modo per separare la logica aziendale dal nostro codice boilerplate e proteggere il codice dell'applicazione dai cambiamenti aziendali.

In un precedente articolo su Java Rule Engine, abbiamo menzionato la specifica JSR 94. Il Jess Rule Engine ha un'importanza particolare come implementazione del driver delle regole di riferimento per JSR 94 , quindi diamo un'occhiata a questo.

2. Jess Rule Engine

Jess è uno dei primi motori di regole ad essere facilmente integrato con Java. Jess utilizza un'implementazione avanzata dell'algoritmo Rete altamente efficiente, rendendolo molto più veloce di un semplice ciclo Java per la maggior parte degli scenari.

Le regole possono essere eseguite da set di regole scritti nel linguaggio nativo di Jess Rules, una sintassi estesa basata su Lisp o da un formato XML più dettagliato. Useremo il formato nativo.

C'è un IDE basato su Eclipse per lo sviluppo (per le versioni precedenti di Eclipse) e un'eccellente documentazione sull'utilizzo e l'integrazione di Jess con Java. C'è anche un'interfaccia della riga di comando REPL in cui possiamo provare le nostre idee prima di creare un file di regole.

Essendo il motore delle regole di riferimento per JSR 94, Jess è per definizione conforme a JSR 94, sebbene non sia più in fase di sviluppo attivo.

2.1. Una breve parola su JSR 94

JSR 94 fornisce un'API che possiamo utilizzare per darci indipendenza da qualsiasi motore di regole che scegliamo. Possiamo collegare qualsiasi motore di regole conforme a JSR 94 nel nostro codice ed eseguire alcune regole senza dover modificare il modo in cui interagiamo con il motore di regole nella nostra applicazione.

Ciò non significa che le regole sottostanti del motore delle regole avranno lo stesso aspetto: potremmo doverle riscrivere se cambiamo il motore delle regole, ma significa che non avremo bisogno di riscrivere parti della nostra applicazione per utilizzare il nuovo motore delle regole. Le uniche modifiche al codice di cui avremo bisogno sono l'aggiornamento del nome del driver e di alcuni nomi di file di regole.

2.2. Il driver Jess JSR 94

Sebbene per JSR 94 sia incluso un driver del motore delle regole di riferimento per Jess, Jess stesso non è incluso, poiché si tratta di un prodotto commerciale con licenza. Il driver di riferimento è contenuto nel pacchetto org.jcp.jsr94.jess , ma un driver più recente è disponibile nel pacchetto jess.jsr94 quando scarichiamo Jess.

Iniziamo esaminando l'integrazione Java nativa di Jess prima di passare a vedere come il livello JSR 94 cambia questo.

3. Esempi forniti

Prima di iniziare a integrare Jess nel nostro codice, assicuriamoci di averlo scaricato e di averlo reso disponibile sul nostro classpath. Dovremo registrarci per il download di prova gratuito di 30 giorni a meno che non abbiamo già una licenza.

Quindi, scarichiamo Jess, scompattiamo il file Jess71p2.jar scaricato ed eseguiamo uno dei suoi esempi per assicurarci di avere una versione funzionante.

3.1. Jess autonomo

Diamo un'occhiata alla directory Jess71p2 / examples , dove la directory jess contiene alcuni set di regole di esempio. La directory pricing_engine mostra un'integrazione che può essere eseguita tramite uno script ant build.xml . Cambiamo la nostra directory all'esempio del motore di determinazione dei prezzi ed eseguiamo il programma tramite ant test :

cd Jess71p2/examples/pricing_engine ant test

Questo costruisce ed esegue un esempio di regole per la determinazione del prezzo:

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 con JSR 94

Ora che abbiamo Jess al lavoro, scarichiamo JSR 94 e quindi decomprimilo per creare una directory jsr94-1.0 con le directory ant, doc, lib e src all'interno.

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

Questo ci fornisce l'API JSR 94 e il driver di riferimento Jess, ma non viene fornito con l'implementazione Jess con licenza, quindi se proviamo a eseguire un esempio ora, otterremo il seguente errore:

Error: The reference implementation Jess could not be found.

Quindi, aggiungiamo l'implementazione di riferimento Jess, jess.jar , che fa parte di Jess71p2 che abbiamo scaricato in precedenza e copiamola nella directory lib di JSR 94, quindi eseguiamo l'esempio:

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

L'esempio esegue alcune regole per determinare il credito residuo di un cliente quando le fatture vengono pagate:

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. Integrazione di Jess con Java

Ora che abbiamo scaricato Jess e JSR 94 e abbiamo eseguito alcune regole sia in modo nativo che tramite JSR, diamo un'occhiata a come integrare un set di regole Jess in un programma Java.

Nel nostro esempio, inizieremo eseguendo un semplice file di regole Jess, hellojess.clp, dal codice Java, e poi guarderemo un altro file di regole, bonus.clp , che utilizzerà e modificherà alcuni dei nostri oggetti.

4.1. Dipendenza da Maven

Non è disponibile alcuna dipendenza Maven per Jess, quindi se non lo abbiamo già fatto, scarichiamo e decomprimiamo il vaso Jess ( jess.jar ) e mvn lo installiamo nel nostro repository Maven locale:

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

Possiamo quindi aggiungerlo come dipendenza nel solito modo:

 gov.sandia jess 7.1p2 

4.2. Ciao Jess Rules File

Successivamente, creiamo il più semplice dei file di regole per stampare un messaggio. Salveremo il file delle regole come 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.

Se abbiamo regole per la stessa logica di business, scritte per un altro motore di regole conforme a JSR 94, allora possiamo semplicemente aggiungere il driver per il nostro motore di regole alternativo e aggiornare il nome del driver che la nostra applicazione dovrebbe usare, e non dovrebbero essere apportate ulteriori modifiche al codice necessario.

Ci sono maggiori dettagli su jess.sandia.gov per l'incorporamento di Jess in un'applicazione Java e Oracle ha una guida utile per iniziare con l'API del motore di regole Java (JSR 94).

Come al solito, il codice che abbiamo visto in questo articolo è disponibile su GitHub.