Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: How to execute all the selected rules' actions instead of highest salience? #89

Closed
sourcesoft opened this issue Aug 14, 2020 · 11 comments

Comments

@sourcesoft
Copy link
Contributor

sourcesoft commented Aug 14, 2020

Hi, I'm a bit confused or lost with this scenario, wonder if you could guide me: Imagine I have 20 rules. And with a given fact, 3 of them pass. The conflict set chooses the highest priority out of those 3. However I need all 3 to be executed (to run the action or then clause).

Use case: I have a triggers/automations system that one trigger (an event like page created) could dispatch multiple actions (if the rules or conditions are met). Suppose that the user have created 2 automations (each automation consists of a trigger + GRL that has the action as a fact method) that their associated rules both pass for a given page creation event/trigger. One trigger/automation will send an email (one action or fact method), another will send a notification (another).

Couldn't find a way to achieve this. Is there a better way to do this or am I missing something?

@jinagamvasubabu
Copy link
Collaborator

Looks like you are expecting something like fireAllRules(max) kind of method, if i compare with drools. Basically fireAllRules methods executes all the rules which are matching a fact and even it takes a parameter max to run how many matching rules

Coming to the problem statement - i am not sure do we really require fireAllRules kind of method like drools @newm4n - any inputs here ?
One quick and dirty way would be combine all these three rules (matching a fact) into one if all are interconnected ?

correct me if i am missing anything ?

@sourcesoft
Copy link
Contributor Author

sourcesoft commented Aug 14, 2020

Yes, I think fireAllRules is what I was looking for, that doesn't try to resolve the conflict set with priority but instead execute them all.

One quick and dirty way would be combine all these three rules (matching a fact) into one if all are interconnected ?

If I combine all 20 or 3 rules together with a OR operator (i think that's the solution) it will only run the rule's action once. However each of these rules have their own unique/common set of actions and one trigger could fire off multiple rules=>actions combo

@sourcesoft
Copy link
Contributor Author

I found that we have FetchMatchingRules too, which is useful but it only finds the matches and doesn't execute each rule's action(s).

@jinagamvasubabu
Copy link
Collaborator

yes, FetchMatchingRules was recently introduced by me to identify all the matching rules against a fact.

lets have this thread open and see what others feel about fireAllMatchingRules function

@newm4n
Copy link
Member

newm4n commented Aug 14, 2020

Executing all matching rule's THEN statements could lead to un-necessary complexity.

  1. Then statement could modify facts that might cause other rule become invalid.
  2. If you want to execute those statements in multiple rules, in what order ? salience ? user (business users) will even more confused.
  3. The original idea of rule engine, like 1st chapter of Jboss document about Rule Engine mentioned, Is to tell the calling system "What todo" but not "How to do it".
  4. Im a bit worried to have an idea to make Grule to match the complexity of Drools. Its an open source project thus every body can fork it and add more functionality them self.

To fulfil your case, I can think of couple work around. One simple thing you can do, to have a fact containing flags, a couple boolean attributes. Add this fact into data context and have the rules switch on and off those flags. When the rule ends, you know what is on, and what is off and execute some functions based on that. The benefit as follows

  1. You can plan better of what to execute in your own golang code. Let the rule switch what to do, but your code are the one who do it.
  2. Its much simpler to make the rule(s) this way, and easier to debug.
  3. Its consistent. and avoid complexities and surprises.

Rule engine is declarative computational approach and should be used for cases where often too complicated to do in conventional programming logic. There are DOs and DONTs when using Rule engine and cases where rule engine do best, meaning that its not a silver bullet of every thing. I advise you to read Martin Fowler's Rule Engine page.

@jinagamvasubabu
Copy link
Collaborator

jinagamvasubabu commented Aug 14, 2020

i really got your point and totally understand to make grule simple and robust.

agree with you

@sourcesoft
Copy link
Contributor Author

Thanks for the detailed explanation. I agree with you on all these points. As Martin says

An important property of rule engines is chaining - where the action part of one rule changes the state of the system in such a way that it alters the value of the condition part of other rules. Chaining sounds appealing, since it supports more complex behaviors, but can easily end up being very hard to reason about and debug.

It makes the system hard to reason about. In my case, the final actions are fairly simple and are acting as notifications and I think is a common requirement by these systems.

One simple thing you can do, to have a fact containing flags, a couple boolean attributes. Add this fact into data context and have the rules switch on and off those flags. When the rule ends, you know what is on, and what is off and execute some functions based on that.

I also agree with the benefits of this approach. However as I understand your solution:

have the rules switch on and off those flags

switching the flags on and off is only possible and must occur during THEN statement, which runs if the rule conditions are met and that rule has the highest priority. Which brings me back to my original problem, that is I can not switch those flags for every rule but only one (even though the engine goes through all the rules' conditions once, it only execute one singular selected rule).

@newm4n
Copy link
Member

newm4n commented Aug 15, 2020

A bunch of facts.
One of the facts containing flags that the rule can turn on and of.
Other fact were business process related.

type Flags struct {
	RuleA bool
	RuleB bool
	RuleC bool
	RuleD bool
} 

type Fact struct {
	Name string
}

Then, we specify some rule, Between these rule they have a "chaining" mechanism.

rule RuleA "Turn On RuleA flag if condition is met" {
	when
		Flags.RuleA == false &&
		Fact.Name == "George"
	then
		Flags.RuleA = true;
		Retract("RuleA");
}

rule RuleB "Turn On RuleB flag if condition permit" {
	when
		Flags.RuleB == false &&
		Fact.Name == "George"
	then
		Flags.RuleB = true;
		Retract("RuleB");
}

rule RuleC "Turn On RuleC flag if some fact are ok"  {
	when
		Flags.RuleC == false &&
		Fact.Name == "Lucy"
	then 
		Flags.RuleC = true;
		Retract("RuleC");
}

rule RuleD "Turn On RuleD flag when necessary" {
	when
		Flags.RuleD == false &&
		Fact.Name == "Lucy"
	then 
		Flags.RuleD = true;
		Retract("RuleD");
}

We add the facts into our datacontext, and execute

Flags := &Flags{}
Fact := &Fact { Name: "Lucy" }
dataContext := ast.NewDataContext()
err := dataContext.Add("Flags", Flags)
err := dataContext.Add("Fact", Fact)
...
...
eng1 := &engine.GruleEngine{}
err := eng1.Execute(dataContext, kb)

When the rule execution by the engine is finished,
you will see that Flags struct instance will have their
booleans variables were configured according the rules.

Then your golang code take over what to do what every you want with the fact.

if Flags.Fd {
	// Notify the user that they are Suberb !!
	fmt.Println("Superb")
}
if Flags.Fc {
	// Notify the user that they are Awesome !!
	fmt.Println("Awesome")
}
if Flags.Fa {
	// Notify the user that they are Cool !!
	fmt.Println("Cool")
}
if Flags.Fb {
	// Notify the user that they are Great !!
	fmt.Println("Great")
}

I hope you understand.

@sourcesoft
Copy link
Contributor Author

Thank you so much, I understand your approach now. I missed the usage of Retract, and how the cycle repeats again as long as there's an action that need execution. Also didn't know eng.Execute(dataContext, kb) will change the dataContext itself (didn't seem to be a pointer) and I can run my code after eng.Execute consequently once it was completed. I thought my code could only be executed in the fact methods and if I wanted to do something with the flags at the end I should add a custom fact.method and call it manually at the end in THEN clause as part of the rule definition.

I'm storing my rules without these extra facts/flags and Retract and retrieve them from an outside source. I wonder if there's a programatic way of adding these expressions (rule assignment and retract) to the rule right before execution and keep the original ones clean and not confusing. I'll dig into the expressions methods but if you guys know how already I'll appreciate it if you could point me to the right direction. Thank you again for all the help already.

@newm4n
Copy link
Member

newm4n commented Aug 15, 2020

Im happy that you get it.

Thats what people on Jboss and Martin Folwer mean by "Rule Engine tell you what to do, but not how to do it". The rule engine is the one to check and validating things, applying some values and hints. But in the end, your code is the one who going to do it, even though, with grule and drools the engine are some how capable of doing it (can call function to do the heavy lifting), but its not advised. Many rule engine only implement the "When" part. Grule and Drools also implement the "Then" part.

In real business case, according to some set of Facts, Rule Engine would specify tax value, discounts, amount of chemical substance, estimating things and decide some final values. But thats about it. In the end, your code would take the values produced by rule engine and apply them to real operation. To send notification, to save into database, or to turn the steering wheel left or right.

@rifqifatih
Copy link
Contributor

A bunch of facts.
One of the facts containing flags that the rule can turn on and of.

@newm4n in this case, how would you handle different saliences? E.g. salience RuleA == RuleB == RuleC == 10, RuleD == 1. I expect it should flag A,B,C but not D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants