Learn how to integrate the Otter Rules Engine inside your Angular
application.
In this page, you will not find descriptions of the different concepts of the Rules Engine. If a concept seems a
bit confusing, please refer to the Rules Engine Introduction.
Pre-requisites:
[!INFO] You can find a live example on a rules engine integration in the Otter showcase with references to the implementation code. Do not hesitate to explore the demo and its implementation code to get a better understanding of the rules engine capabilities.
Here, we will go step by step to provide you with a better understanding of the implementation.
Let's integrate the showcase ruleset in an application. We assume the pre-requisites are met.
As a first use case, we keep it simple. We will consider only the first rule of the showcase rulesets.
In this rule, if the user selects a date during the summer, New-York will be added in the list of destinations.
This Ruleset only makes reference to an action already provided by the Otter framework.
For this example, let's consider the outboundDate
fact in the Otter showcase fact folder and the
UPDATE_CONFIG
action provided in the @o3r/configuration
package and the duringSummer
operator defined in the Otter showcase operator folder.
Look into the Going Further section to look for more complex and optimized use cases.
First, the RulesEngineRunnerModule
should be imported in your application module (see showcase application configuration).
The runner is the core of the rules engine. This is where all the Rulesets will be processed to identify the resulting actions.
The result of the rules engine is a set of actions applied at the runtime of the application.
The engine is agnostic of the implementation of the different actions in your Rulesets. It allows for extensibility and a better tree shaking as you will not import useless code to handle actions you do not need.
This means that you will have to explicitly import the action handling modules and register them inside the rules engine.
You will not find the Otter actions in the @o3r/rules-engine
package but in the ones of the feature they impact:
UPDATE_ASSET
: requires the import of AssetRulesEngineActionModule
from @o3r/dynamic-contentUPDATE_LOCALISATION
: requires the import of LocalizationRulesEngineActionModule
from @o3r/localizationUPDATE_CONFIG
: requires the import of ConfigurationRulesEngineActionModule
from @o3r/configurationUPDATE_PLACEHOLDER
: requires the import of PlaceholderRulesEngineActionModule
and PlaceholderRequestStoreModule
from @o3r/componentsOnce the modules have been imported by the application, they need to be explicitly registered into the Rules Engine in the pages where they will be used. For instance, in the showcase, this is done in the rules engine page component,
Note that in the 'New-York availability in summer rule', we only need to register the UPDATE_CONFIG
action.
[!WARNING] In case the Rules Engine results in an unregistered action, a warning will be raised. You will be able to access it thanks to the rules engine debug tools.
Now that the rules engine recognizes the UPDATE_CONFIG
action, it needs to recognize the outboundDate
fact
which should emit values when the outboundDate changes.
In the Otter showcase, this fact is exposed by the tripFactService
.
Look into the Going Further section to find out how to create your own fact.
As with actions, the rules engine is not aware of the facts that will be used in the Ruleset. This is not only because listening and processing useless facts might impact the performance of the rules engine, but also that facts are generally business oriented, and thus they cannot be built-in the framework.
Hence, you will need to explicitly register your fact. Again, in the showcase, this is done in the rules engine page component.
Thanks to this, the application is now ready to retrieve and evaluate the rules and execute the resulting actions.
[!NOTE] There are no constraints on whether to register facts before setting up the Rulesets. The ruleset using them will just fail until the facts are available. Once the fact becomes available, a new evaluation of the Ruleset will be triggerred.
As we rely on a custom duringSummer
operator, it also needs to be registered in the rules engine (see showcase rules engine implementation)[https://github.com/AmadeusITGroup/otter/blob/main/apps/showcase/src/app/rules-engine/rules-engine.component.ts#L99].
The last step is quite simple, just retrieve your JSON file with all the Rulesets you want to run and set them up in the rule engine runner (see showcase example).
To help you set up your rules engine, the Otter team provides different tools:
import {inject, runInInjectionContext} from '@angular/core';
import {OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, RulesEngineRunnerModule, RulesEngineDevtoolsModule, RulesEngineDevtoolsConsoleService} from '@o3r/rules-engine';
import {AppComponent} from './app.component';
bootstrapApplication(AppComponent, {
imports: [
RulesEngineDevtoolsModule,
RulesEngineRunnerModule.forRoot({debug: true}) // Activate rule engine debug mode
],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [
{provide: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true}}
]
})
.then((m) => {
runInInjectionContext(m.injector, () => {
inject(RulesEngineDevtoolsConsoleService);
});
})
// eslint-disable-next-line no-console
.catch(err => console.error(err));
In debug mode, the rules engine exposes methods to get the latest run and status of each Ruleset and the actions enabled:
Example :// Get the list of active rulesets
_OTTER_DEVTOOLS_.rulesEngine.getActiveRulesets();
// Get the list of output actions, temporary facts, fact triggers and rules for a Ruleset
_OTTER_DEVTOOLS_.rulesEngine.getRulesetExecutions(rulesetId);
// Get all the actions currently applied
_OTTER_DEVTOOLS_.rulesEngine.getAllOutputActions();
If you don't want to generate and maintain your Ruleset JSON file manually, you might want to build a UI that will do it for you. For this use case, you will need Metadata files with the list of supported operators, facts and the actions that can be processed on your application (configurations, placeholders, translations etc.).
You can find more information on how to industrialize your Ruleset generation in the dedicated documentation.
If the Otter rules engine comes with built-in actions, operators and one integrated fact, you will need your own custom facts and maybe add your own custom operators and actions.
Check out the dedicated sections:
If you want to measure the performance of your Rulesets, the Otter rules engine exposes a set of metrics. Learn more about them in the performance documentation.
Check out some classic examples to build your first Ruleset:
An action can result in the update of a runtime variable scoped within the Ruleset. Other rules of the same Ruleset can then make reference to the runtime variable.
This can lead to factorization as it is then possible to use runtime facts as a way to represent a complex condition tree. You can find an example of this feature in the runtime fact documentation.
While it is the norm and recommendation to keep the operators of rules pure, there might be cases where a pure operator does not make any sense.
This is the case of dateInNextMinutes
and dateNotInNextMinutes
.
In those use cases, the reference to a current time fact is implicit and a current time fact cannot really be used as an explicit parameter. For this reason, the Otter framework introduces the concept of implicit 'built-in' facts.
Check out how to integrate those operators and override the default implicit fact behavior in the built-in fact documentation.
If your Ruleset only affects a component to modify its configuration or one of its translation key, you won't need it to
run on pages where this component is missing. You can restrict the evaluation of your Ruleset to only pages with your
component thanks to the linkedComponents
property and improve the performances of your application.
Check out the linkedComponents documentation.
You can also base your rules on the parameters of your application to deliver different user experiences depending on deeplinks.
For example, you could leverage this to do A/B testing by sending a parameter which can take different values and by creating rules based on this parameter's values.
As this use case heavily relies on the integration with other websites, it is preferable to avoid shipping a new version of your application everytime you want to use a new parameter.
One way to answer all these requirements is to use portalFacts
.
Check out how to implement them in the portal facts documentation.