Structure des Fichiers de Règles Drools
Dans l'écosystème Drools, la définition des règles métier s'effectue principalement via des fichiers texte avec l'extension .drl. Ce suffixe est l'abréviation de Drools Rule Language, le langage spécifique pour écrire les règles. Un fichier DRL complet est typiquement agencé comme suit :
- Déclaration de package : Le mot-clé
packageest utilisé pour regrouper logiquement les règles. Contrairement aux packages Java, il s'agit d'une convention organisationnelle au sein du moteur de règles. - Importations de classes : La section
importpermet d'importer des classes Java externes, de la même manière que dans le code Java standard. Ces classes représentent les objets métier (faits) avec lesquels les règles interagiront.
Il est à noter que Drools prend également en charge d'autres formats pour la définition de règles, notamment les tables de décision basées sur Excel.
Anatomie d'une Règle
Le cœur de chaque fichier DRL est constitué d'une ou plusieurs règles. Chaque règle encapsule une portion de logique métier et suit une structure syntaxique précise :
rule "NomDeLaRegleUnique"
[attributs]
when
# Conditions (LHS - Left Hand Side)
then
# Actions (RHS - Right Hand Side)
end
rule: Mot-clé obligatoire qui marque le début d'une nouvelle règle. Il est suivi d'un nom de règle unique, généralement encadré par des guillemets.attributs: Section facultative où des propriétés spécifiques à la règle peuvent être définies, commesaliencepour la priorité ouno-looppour éviter les exécutions répétées.when: Mot-clé qui introduit la partie conditionnelle de la règle (LHS).- LHS (Left Hand Side) : C'est la section des conditions. Elle est composée d'un ou plusieurs éléments de condition (motifs) qui doivent être satisfaits pour que la règle soit activée. Une LHS vide est considérée comme toujours vraie.
then: Mot-clé qui introduit la partie d'action de la règle (RHS).- RHS (Right Hand Side) : C'est la section des actions ou des conséquences. Le code Java contenu ici est exécuté lorsque la règle est activée.
end: Mot-clé obligatoire qui signale la fin de la définition de la règle.
Commentaires dans le Langage DRL
Les commentaires sont essentiels pour documenter et rendre les règles compréhensibles. Le langage DRL prend en charge deux types de commentaires, similaires à Java :
- Commentaires sur une seule ligne : Ils commencent par
//et s'étendent jusqu'à la fin de la ligne. - Commentaires sur plusieurs lignes : Ils débutent par
/*et se terminent par*/, pouvant couvrir plusieurs lignes de texte.
Voici des exemples illustratifs :
// Cette règle accorde une remise pour les articles de grande valeur.
rule "RemiseArticlesPremium"
when
// Un article doit avoir un prix élevé.
then
System.out.println("Règle de remise Premium déclenchée.");
end
/*
Cette section contient une règle pour les commandes urgentes.
Elle s'assure qu'elles sont traitées rapidement.
*/
rule "TraitementCommandeUrgente"
when
// Vérification du statut urgent de la commande.
then
System.out.println("Traitement de commande urgente initié.");
end
Correspondance de Motifs (Pattern Matching)
La puissance de Drools réside dans sa capacité à faire correspondre des "motifs" avec les objets (appelés faits) présents dans la mémoire de travail. La section LHS d'une règle est l'endroit où ces motifs sont définis.
La structure d'un motif est la suivante : $nomVariable: TypeDeFait(contrainte1, contrainte2, ...)
$nomVariable: Il s'agit d'une variable liée facultative, dont le nom commence généralement par$. Si elle est définie, elle permet de faire référence à l'objet correspondant dans la section RHS de la règle.TypeDeFait: C'est le type de l'objet Java que le moteur de règles doit rechercher dans sa mémoire de travail.contrainte1, contrainte2, ...: Ce sont des expressions qui filtrent les objets duTypeDeFaitspécifié. Elles doivent évaluer àtruepour que la condition soit remplie.
Exemple de motif simple
Considérons une règle pour identifier des produits en rupture de stock :
rule "AlerteRuptureDeStock"
when
// Un produit est en rupture si son stock disponible est égal à 0
$produit: Produit(stockDisponible == 0)
then
System.out.println("Alerte: Le produit '" + $produit.getNom() + "' est en rupture de stock.");
end
Cette règle s'activera si un objet de type Produit est inséré dans la mémoire de travail et que son attribut stockDisponible est à zéro.
Liaison de variable à une propriété
Il est également possible de lier une variable non pas à l'objet entier, mais à une de ses propriétés :
rule "VerifierNiveauPriorite"
when
$tache: Tache($niveau: priorite > 7)
then
System.out.println("Tâche avec une priorité élevée (" + $niveau + ") détectée : " + $tache.getDescription());
end
Ici, $niveau est une variable liée qui contiendra la valeur de l'attribut priorite de l'objet Tache.
Motifs multiples et opérateurs logiques
La section LHS peut contenir plusieurs motifs. Par défaut, ces motifs sont implicitement combinés avec un opérateur logique and. Vous pouvez également utiliser explicitement and ou or :
rule "PromotionClientFidele"
when
$client: Client(anciennete > 5 && statut == "Gold")
$achat: Commande(montantTotal > 500)
then
System.out.println("Client Gold fidèle (" + $client.getNom() + ") effectuant un gros achat. Appliquer une promotion spéciale.");
// Logique pour appliquer la promotion
end
Opérateurs de Comparaison Avancés
Drools fournit, en plus des opérateurs de comparaison arithmétiques standards (==, !=, <, <=, >, >=), des opérateurs spécifiques pour des comparaisons plus complexes :
1. contains | not contains
Ces opérateurs permettent de vérifier si une chaîne de caractères contient une sous-chaîne ou si une collection/un tableau contient un élément donné.
rule "NomDeProduitAvecMotCle"
when
$produit: Article(nom contains "Édition Limitée")
then
System.out.println("Article spécial: " + $produit.getNom());
end
rule "DescriptionSansTermeInterdit"
when
$produit: Article(description not contains "défectueux")
then
System.out.println("Description OK pour l'article: " + $produit.getNom());
end
2. memberOf | not memberOf
Ces opérateurs vérifient si la valeur d'un champ est présente (ou absente) dans une collection ou un tableau spécifié.
rule "ArticleDansCategoriesPrincipales"
when
$categoriesPrincipales: List( ) from {"Électronique", "Vêtements"}
$article: Article(categorie memberOf $categoriesPrincipales)
then
System.out.println("Article de catégorie principale: " + $article.getNom());
end
rule "ArticleHorsCategoriesRestreintes"
when
$categoriesRestreintes: List( ) from {"Armes", "Drogues"}
$article: Article(categorie not memberOf $categoriesRestreintes)
then
System.out.println("Catégorie de l'article autorisée: " + $article.getNom());
end
3. matches | not matches
Ces opérateurs permettent d'effectuer une correspondance de motifs sur des chaînes de caractères en utilisant des expressions régulières Java.
rule "CodeSKUSpecifique"
when
$article: Article(sku matches "SKU-[A-Z]{3}[0-9]{4}") // Par exemple SKU-ABC1234
then
System.out.println("Code SKU conforme: " + $article.getSku());
end
rule "EmailNonValide"
when
$client: Client(email not matches "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$")
then
System.out.println("L'adresse email du client " + $client.getNom() + " ne semble pas valide.");
end
Classe d'Entité pour les exemples d'opérateurs
Pour illustrer ces opérateurs, nous utiliserons les classes Article et Client :
package com.exemple.drools.modele;
import lombok.Data;
import java.util.List;
@Data
public class Article {
private String nom;
private String description;
private String categorie;
private String sku; // Stock Keeping Unit
private List<String> tags;
private double prix;
}
@Data
public class Client {
private String nom;
private String email;
private int anciennete; // en années
private String statut; // "Gold", "Silver", etc.
}
Test Java des opérateurs de comparaison
Voici un exemple de code Java pour insérer des faits et déclencher les règles avec ces opérateurs :
import org.junit.jupiter.api.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import com.exemple.drools.modele.Article;
import com.exemple.drools.modele.Client;
import java.util.Arrays;
public class TestOperateursDrools {
@Test
public void executerReglesOperateurs() {
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-drools-examples"); // Assurez-vous que le nom de la session est correct dans kmodule.xml
// Création d'un article
Article article = new Article();
article.setNom("Ordinateur portable 'Édition Limitée'");
article.setDescription("Un puissant ordinateur portable pour gamers. Pas de défaut.");
article.setCategorie("Électronique");
article.setSku("SKU-PRO1001");
article.setTags(Arrays.asList("gaming", "haut-de-gamme"));
article.setPrix(1500.0);
// Création d'un client
Client client = new Client();
client.setNom("Jean Dupont");
client.setEmail("jean.dupont@exemple.com");
client.setAnciennete(7);
client.setStatut("Gold");
kSession.insert(article);
kSession.insert(client);
kSession.fireAllRules();
kSession.dispose();
}
}
Exécution Ciblée de Règles
Par défaut, l'appel à kieSession.fireAllRules() exécute toutes les règles dont les conditions sont remplies. Si vous sohuaitez limiter l'exécution à des règles spécifiques, Drools permet d'utiliser des filtres d'agenda. Le RuleNameEqualsAgendaFilter est un exemple courant pour n'exécuter qu'une règle par son nom.
Voici comment exécuter une seule règle nommée "NomDeProduitAvecMotCle" :
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.drools.core.base.RuleNameEqualsAgendaFilter; // La classe se trouve généralement dans drools-core
import com.exemple.drools.modele.Article;
// ... (Initialisation KieServices, KieContainer, KieSession)
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-drools-examples");
Article articlePourTest = new Article();
articlePourTest.setNom("Téléviseur 'Édition Limitée'");
articlePourTest.setDescription("TV 4K Ultra HD.");
articlePourTest.setCategorie("Électronique");
articlePourTest.setSku("SKU-TV1234");
articlePourTest.setPrix(900.0);
kSession.insert(articlePourTest);
// Exécute uniquement la règle dont le nom correspond à "NomDeProduitAvecMotCle"
kSession.fireAllRules(new RuleNameEqualsAgendaFilter("NomDeProduitAvecMotCle"));
kSession.dispose();
Mots-clés de Drools
Les mots-clés dans Drools sont classifiés en deux catégories :
- Mots-clés "durs" (Hard keywords) : Ce sont des termes réservés qui ne peuvent pas être utilisés comme identificateurs (noms de package, de règle ou de variable) sans être placés entre guillemets. Exemples :
true,false,null. Par exemple, écrirerule trueest invalide, maisrule "true"est syntaxiquement correct. - Mots-clés "mous" (Soft keywords) : Ces mots-clés sont principalement des opérateurs, des noms d'attributs ou des constructions DRL. Bien qu'il soit techniquement possible de les utiliser comme identificateurs dans certains contextes (par exemple, un nom de variable Java dans le RHS), c'est fortement déconseillé pour éviter la confusion et maintenir la lisibilité. Quelques exemples incluent :
package,import,rule,when,then,end,salience,no-loop,eval,not,exists,and,or,from,collect,accumulate.
Méthodes Intégrées pour la Manipulation des Faits
La section RHS (partie then) d'une règle Drools n'est pas limitée à de simples affichages. Elle peut interagir directement avec la mémoire de travail du moteur de règles en insérant, modifiant ou retirant des faits. Ces actions peuvent, à leur tour, déclencher d'autres règles, créant un comportement réactif.
Nous utiliserons une entité simple, Employe, pour nos démonstrations :
package com.exemple.drools.modele;
public class Employe {
private String id;
private String nom;
private int anciennete; // en années
private double salaire;
public Employe(String id, String nom, int anciennete, double salaire) {
this.id = id;
this.nom = nom;
this.anciennete = anciennete;
this.salaire = salaire;
}
// Getters et Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public int getAnciennete() { return anciennete; }
public void setAnciennete(int anciennete) { this.anciennete = anciennete; }
public double getSalaire() { return salaire; }
public void setSalaire(double salaire) { this.salaire = salaire; }
@Override
public String toString() {
return "Employe{" +
"id='" + id + '\'' +
", nom='" + nom + '\'' +
", anciennete=" + anciennete +
", salaire=" + salaire +
'}';
}
}
1. La méthode update
La méthode update($fact) est utilisée pour notifier le moteur de règles qu'un fait dans la mémoire de travail a été modifié. Suite à cette notification, le moteur réévalue toutes les règles qui pourraient être affectées par cette modification. Il est essentiel de l'utiliser judicieusement pour éviter les boucles infinies de réévaluation.
Exemple DRL avec update :
package gestionRH
import com.exemple.drools.modele.Employe
rule "AugmenterSalaireJeuneRecrue"
when
$e: Employe(anciennete < 2, salaire < 3000)
then
System.out.println("Augmentation de salaire pour jeune recrue: " + $e.getNom());
$e.setSalaire($e.getSalaire() * 1.10); // Augmente le salaire de 10%
update($e); // Notifie le moteur que l'objet Employe a été modifié
end
rule "PromouvoirEmployeSenior"
when
$e: Employe(anciennete >= 5, salaire >= 3000)
then
System.out.println("L'employé " + $e.getNom() + " est éligible à la promotion.");
// Pas d'update ici si aucune autre règle n'a besoin de réagir à cette modification.
end
Test Java pour update :
import org.junit.jupiter.api.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import com.exemple.drools.modele.Employe;
public class GestionRHTest {
@Test
public void testerUpdateMethod() {
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-drools-examples");
Employe nouvelleRecrue = new Employe("E001", "Alice", 1, 2800.0);
System.out.println("Avant insertion: " + nouvelleRecrue);
kSession.insert(nouvelleRecrue);
System.out.println("Avant exécution des règles: " + nouvelleRecrue);
kSession.fireAllRules();
System.out.println("Après exécution des règles: " + nouvelleRecrue);
kSession.dispose();
}
}
Si la règle "AugmenterSalaireJeuneRecrue" est déclenchée, l'objet nouvelleRecrue est mis à jour et update($e) est appelé. Cela peut entraîner une nouvelle réévaluation des règles. Dans cet exemple, si l'augmentation porte le salaire au-delà de 3000, l'employé pourrait alors satisfaire la condition de la règle "PromouvoirEmployeSenior" dans un cycle d'évaluation subséquent.
2. La méthode insert
La méthode insert($newFact) ajoute un nouvel objet à la mémoire de travail de la session Drools. L'insertion d'un nouveau fait déclenche toujours une réévaluation des règles, car de nouvelles correspondances de motifs pourraient être établies avec ce fait.
Exemple DRL avec insert :
package gestionRH
import com.exemple.drools.modele.Employe
rule "DetecterEmployeTresAncien"
when
$e: Employe(anciennete > 10)
then
System.out.println("Employé très ancien détecté: " + $e.getNom() + ". Création d'une proposition de mentorat.");
Employe propositionMentor = new Employe("M" + $e.getId(), "Proposition Mentorat pour " + $e.getNom(), 0, 0.0);
insert(propositionMentor); // Insère un nouveau fait (une proposition)
end
rule "NotifNouveauMentor"
when
$pm: Employe(id startsWith "M") // Correspond à un fait inséré par la règle précédente
then
System.out.println("Une nouvelle proposition de mentorat a été créée pour: " + $pm.getNom().replace("Proposition Mentorat pour ", ""));
end
Test Java pour insert :
import org.junit.jupiter.api.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import com.exemple.drools.modele.Employe;
public class GestionRHTest {
@Test
public void testerInsertMethod() {
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-drools-examples");
Employe employeSenior = new Employe("E002", "Bernard", 12, 4500.0);
Employe employeJunior = new Employe("E003", "Carla", 3, 3200.0);
kSession.insert(employeSenior);
kSession.insert(employeJunior);
System.out.println("Faits dans la session avant exécution:");
kSession.getObjects().forEach(System.out::println);
kSession.fireAllRules();
System.out.println("Faits dans la session après exécution:");
kSession.getObjects().forEach(System.out::println);
kSession.dispose();
}
}
Lors de l'insertion d'employeSenior, la règle "DetecterEmployeTresAncien" se déclenche et insère un nouvel objet Employe (représentant une proposition de mentorat). Cette nouvelle insertion déclenche la règle "NotifNouveauMentor", qui réagit à la présence de la proposition.
3. La méthode retract
La méthode retract($fact) supprime un fait de la mémoire de travail du moteur de règles. Comme pour insert et update, la suppression d'un fait entraîne une réévaluation des règles, car des correspondances existantes pourraient être invalidées.
Exemple DRL avec retract et salience :
package gestionRH
import com.exemple.drools.modele.Employe
rule "ArchiverEmployeRetire"
salience 10 // Haute priorité pour que cette règle soit évaluée en premier
when
$e: Employe(anciennete > 20, salaire > 5000)
then
System.out.println("Archivage de l'employé retiré: " + $e.getNom());
retract($e); // Retire l'employé de la mémoire de travail
System.out.println("Employé " + $e.getNom() + " retiré de la session.");
end
rule "VerifierPrimeDeFidelite"
when
$e: Employe(anciennete > 15) // Vérifie si l'employé est éligible à une prime de fidélité
then
// Cette règle ne se déclenchera PAS pour les employés retirés par la règle précédente
System.out.println("L'employé " + $e.getNom() + " est éligible à une prime de fidélité.");
end
Test Java pour retract :
import org.junit.jupiter.api.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import com.exemple.drools.modele.Employe;
public class GestionRHTest {
@Test
public void testerRetractMethod() {
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-drools-examples");
Employe employeATraiter = new Employe("E004", "David", 25, 6000.0);
Employe autreEmploye = new Employe("E005", "Eve", 18, 4800.0);
kSession.insert(employeATraiter);
kSession.insert(autreEmploye);
System.out.println("Faits dans la session avant exécution:");
kSession.getObjects().forEach(obj -> {
if (obj instanceof Employe) System.out.println(obj);
});
kSession.fireAllRules();
System.out.println("Faits dans la session après exécution:");
kSession.getObjects().forEach(obj -> {
if (obj instanceof Employe) System.out.println(obj);
});
kSession.dispose();
}
}
En insérant employeATraiter, la règle "ArchiverEmployeRetire" (grâce à sa salience élevée) se déclenche en premier et retire cet employé de la mémoire de travail. Par conséquent, la règle "VerifierPrimeDeFidelite" ne trouvera plus cet employé et ne se déclenchera pas pour lui, mais elle pourrait se déclencher pour autreEmploye si ses conditions sont remplies.