La Configuration
Avant de pouvoir mettre en œuvre le moteur de règles, il est nécessaire de le configurer.
Pour ceci, plusieurs solutions s’offrent. Il est possible d’utiliser les API Java offertes, mais cette solution est peu adaptée pour conserver l’avantage de s’éviter un cycle de développement lors de modifications des règles. Il est recommandé d’utiliser les Fabriques de moteur de règles. La construction d’une instance est détaillée sur une page dédiée.
Des utilitaires permettent de gérer de façon centralisée les instances afin de limiter le nombre de lectures de la configuration et de compilations au strict nécessaire.
A noter que les Fabriques prennent en charge la lecture de la configuration, mais également leur écriture. Il est ainsi envisageable de créer une IHM permettant de modifier la configuration utilisant les API Java puis d’utiliser la fabrique pour la sauvegarder.
Il est aussi simple de changer de format de de configuration : il suffit de lire avec la Fabrique acceptant le format d’origine, puis de sauver avec la Fabrique gérant le format cible.
Le Contexte
Les données en entrée sont fournies au moteur de règles grâce à un contexte.
- Le contexte de compilation sera fourni lors de l’étape de compilation.
- Le contexte d’exécution sera fourni lors du Runtime.
- Le contexte peut être instancié automatiquement grâce à une fabrique de contexte.
- Le contexte n’est pas Thread Safe, contrairement à l’instance du moteur de règles.
- Une instance de contexte est liée à une instance de moteur de règles, l’inverse n’est pas vraie.
Le contexte fournit principalement :
- Les bases de lecture et d’écriture. Les accès au beans d’entrées / sortie spécifiées dans le moteur seront toujours effectuées à partir de ces bases, selon le type d’accès.
- Le référentiel de variables.
MRules fournit plusieurs types de contextes :
- Le contexte standalone : l’implémentation la plus simple, avec laquelle aucune données ne sera fournie au moteur de règles. Les règles peuvent s’appuyer sur d’autres données, par exemple l’heure courante.
Les Classes Java associée sont MStandaloneCompilationContext pour la compilation et MStandaloneExecutionContext pour l’exécution. - Le contexte standard : une implémentation simple, permettant de fournir au moteur de règles un seul bean java, qui portera les valeurs d’entrée et de sortie.
Les Classes Java associée sont MCompilationContext pour la compilation et MExecutionContext pour l’exécution. - Le contexte IN / OUT : Une implémentation plus riche, permettant de fournir au moteur de règles deux bean java. L’un portera les valeurs d’entrée et l’autre celles de sortie.
Les Classes Java associées sont MInOutCompilationContext pour la compilation et MInOutExecutionContext pour l’exécution. - Le contexte de liste : permettant la compatibilité avec les cas d’utilisation définis par la JSR94. Il accepte une liste d’Object, qui seront ensuite regroupés selon leur classe et sur lesquels il sera possible d’itérer. Chaque type doit être défini par la configuration du moteur de règles.
Les Classes Java associée sont MListCompilationContext pour la compilation et MListExecutionContext pour l’exécution. - Enfin, on peut également noter la présence d’autres contextes, permettant par exemple de transformer un contexte de compilation en contexte d’exécution (ou inversement), ou bien d’encapsuler un contexte afin de modifier un comportement spécifique. Ces contextes sont dédiés à un usage interne ou leur utilisation est destinées aux développement d’Addons spécifiques.
Le contexte peut être créé soit manuellement (new instance) soit via la fabrique de contexte.
Les trois phases du cycle de vie
Le cycle de vie d’une instance du rule set est constituée de trois phases, détaillées ci-dessous.
La Compilation
Après avoir construit l’instance du moteur de règles, la première étape obligatoire est la compilation.
Cette étape permet d’opérer plusieurs pré-calculs :
- Déterminer les types d’objets nécessaires, et donc les conversions de types qui devront être effectuées.
- Détecter d’éventuelles erreurs, qui ne se produiront ainsi pas au Runtime.
Plusieurs niveaux de compilation sont disponibles :
- Le niveau standard :
- Nécessite de connaître les types des objets en entrée.
- Permet de détecter le plus d’erreur et d’effectuer le maximum de pré-calculs.
- Le niveau léger :
- Ne nécessite pas la connaissance des objets en entrée.
- Permet plus de souplesse lors du Runtime.
La compilation en niveau STANDARD va vérifier que les propriétés sont accessibles dans les beans d’entrée / sortie, en utilisant l’API de réflexion Java. Par exemple, si la configuration essaie d’accéder à la propriété « personne.agge » au lieu de « personne.age », alors les getter / setter ne seront pas trouvés et une erreur sera remontée.
Elle permet également de faire des conversions internes de données, qui ne seront plus faites à l’exécution. Par exemple, si la propriété « personne.age » est un entier et est comparé à « 18 », alors la valeur « 18 » sera transformée en entier.
Seule les signatures des méthodes sont utilisées : les données de sont pas lues. Donc un Objet vide sans données peut être fourni. Il n’est pas nécessaire de le remplir avec des données factices, elles ne seront pas utilisées. Dans certain cas, le bean fourni peut être modifié (en cas de sous-objets par exemple).
Si l’implémentation des beans ne sont pas disponibles, il est possible d’utiliser le niveau de compilation LIGHT. Dans ce cas, la vérification d’accessibilité des propriétés ne sera pas effectuée. Les transformations de données seront faites au mieux : par exemple, la chaine de caractères « true » sera transformée en booléen, la chaine « 21.0 » sera transformée en double.
Sans la compilation, le comportement à l’exécution aboutira sur une Exception dont le code d’erreur est « MRE_COMPILATION_MISSING ». Les Fabriques de moteur de règles peuvent effectuer cette étape de façon transparente.
Sinon, la simple ligne de code suivante lance la compilation :
contexte.compile();
L’Optimisation
Après la compilation, une deuxième phase est l’optimisation. Cette étape facultative va chercher à optimiser les performances lors de l’exécution et à minimiser l’empreinte mémoire. L’optimisation est déclenchée automatiquement à la fin de la compilation, par la ligne de code présentée ci-dessus, sauf si explicitement désactivée.
La désactivation de l’optimisation se fait soit via le contexte de compilation (setOptimizationEnabled), soit via la configuration CDI ou JNDI.
L’optimisation fonctionne en deux temps :
- Tout d’abord, une chaîne d’optimiseurs globaux est exécutée. Les optimiseurs actuels sont :
- Recherche des expressions dont le résultat d’évaluation est constant, et transformation en constantes (exemple : « A OU true » égale « true »).
- Recherche des expressions logiques simplifiables (exemple : « A OU A » égale « A »).
- Optimisation de l’empreinte mémoire en mutualisant les objets similaires.
- Ensuite, optimisation unitaire. La méthode « optimize » de chaque Addon est invoquée.
Chaque étape de la chaîne d’optimiseurs globaux est désactivable unitairement, via les propriétés génériques du rule set (c.f. javadoc de chaque optimiseur pour connaître le libellé de la propriété).
L’Exécution
Une fois créée l’instance du contexte et les beans d’entrée sortie valués et positionnés, l’exécution n’est plus qu’une formalité, lancée par une simple ligne de code java :
contexte.execute();