Multi-tenanted, Dynamic Rule Selection

618 views
Skip to first unread message

Dick Tracy

unread,
Nov 2, 2022, 8:13:26 AM11/2/22
to Drools Usage
We're starting to pick up Drools, working with v8.2.9. Fairly new to BRMS, but we understand the basics.

The existing application is multi-tenanted, with customers logging in and doing some data processing. 

There will be a mix of global rules and some customer specific rules. 

It seems like some combination of agenda groups and agenda filters is the logical way to select rules to be applied for a given set of facts, does that sound correct? 

Our goal is to be able to update rules at runtime without restarting, with the rules stored in a database. It seems we can delete rules from the KieFileSystem, is this considered reasonable practice? The thought there was that we'd track the last compiled version and if it's out of date we'd rebuild.

We're also considering whether it makes sense to segregate rules into separate KieFileSystem instances to try and put a wall between rules that apply to only one particular customer. We'd have to build in handling to feed from one KieFileSystem to the next, but it may be a more reliable way to enforce this separation without managing agenda groups / filters meticulously. 

As of now we expect to be processing fairly large batches of facts with relatively few rules, but this may change as we learn more about the strengths and weaknesses of Drools. Honestly we already seem like we're inversing things, but Drools still seems like a powerful tool for what we're doing.

Thanks in advance for any help from those who have deployed Drools and might be able to give us a heads up on gotchas or things to avoid. And apologies for the long message, I have trouble writing short ones...

Thanks!
  -Dick

Bill Looby

unread,
Nov 2, 2022, 8:25:24 AM11/2/22
to Drools Usage
Just one heads up (and on a slightly different tack I think), you mention 'customer specific rules'. If customers are writing these then there is a huge security concern in any multi-tenant environment (in fact there are security concerns period - they're just worse in multi-tenant).

In PoCs we did, we went for complete segregation of rules with some common utility classes and wasn't that difficult iirc
.

Toshiya Kobayashi

unread,
Nov 11, 2022, 2:03:57 AM11/11/22
to Drools Usage
> Our goal is to be able to update rules at runtime without restarting, with the rules stored in a database. It seems we can delete rules from the KieFileSystem, is this considered reasonable practice? The thought there was that we'd track the last compiled version and if it's out of date we'd rebuild.

You build a KieContainer using KieFileSystem, correct? When you update rules, you should increment its ReleaseId (= GAV) version, then, use a KieContainer of the latest version.


> We're also considering whether it makes sense to segregate rules into separate KieFileSystem instances to try and put a wall between rules that apply to only one particular customer. We'd have to build in handling to feed from one KieFileSystem to the next, but it may be a more reliable way to enforce this separation without managing agenda groups / filters meticulously.

In order to segregate rules, I recommend to create KieContainer with different ReleaseIds. For example, different artifactIds for different customers (e.g. "my-sample-customer-a", "my-sample-customer-b"). KieFileSystem is just a tool to build KieContainers.

This example demonstrates your 2 concerns (updating rules, segregation).

https://github.com/tkobayas/kiegroup-examples/tree/master/Ex-KieFileSystem-multi-KieContainer-em-8.29

I hope it helps.

Toshiya

2022年11月2日水曜日 21:25:24 UTC+9 looby...@googlemail.com:

Adam Belmont

unread,
Mar 30, 2023, 10:52:03 AM3/30/23
to Drools Usage
Hi Folks,

That was a really helpful example, thank you Toshiya.

This thread looked very similar to my use case, except that I'm wondering how to implement the same approach using the RuleUnit java DSL api.
When defineRules() is invoked on my RuleUnit class, I load json from a database, and create the rules based upon the json structure. If the rule in the database is updated, I would like to rebuild the rules defined in the rule unit.
Just making a new RuleUnit instance doesn't work. The defineRules() method will not be called again. (Because of an internal HashMap in org.drools.ruleunits.impl.RuleUniProviderImpl that I refer to later on )

Would a reasonable equivalent to the approach earlier in this thread be to implement NamedRuleUnitData, then alter the name to allow new "versions" of the rule to be generated at rumtime?

Here's an example in this test the RuleUnit uses NamedRuleUnitData.

I'm thinking that each time a new version of a given rule unit is created, then I would have to create a new instance of my rule unit class and increment the name ("MyRule-v2", "MyRule-v3", ....).

 @Test
    public void test() {
        LOG.info("Creating RuleUnit");
        // Let's imagine this knows how to get a rule name. Maybe something like {rule_name}-{tenant_id}-{rule_version}
       // The rule_version portion is incremented if the rule is changed at runtime.
        String name = getRuleName();
        MeasurementUnit measurementUnit = new MeasurementUnit(name);

        RuleUnitInstance<MeasurementUnit> instance = RuleUnitProvider.get().createRuleUnitInstance(measurementUnit);
        try {
            LOG.info("Insert data");
            measurementUnit.getMeasurements().add(new Measurement("color", "red"));
            measurementUnit.getMeasurements().add(new Measurement("color", "green"));
            measurementUnit.getMeasurements().add(new Measurement("color", "blue"));

            LOG.info("Run query. Rules are also fired");
            List<Measurement> queryResult = instance.executeQuery("FindColor").toList("$m");

            assertEquals(3, queryResult.size());
            assertTrue("contains red", measurementUnit.getControlSet().contains("red"));
            assertTrue("contains green", measurementUnit.getControlSet().contains("green"));
            assertTrue("contains blue", measurementUnit.getControlSet().contains("blue"));
        } finally {
            instance.close();
        }

         // Sometime later
         String name = getRuleName();
         // name has now changed
        MeasurementUnit measurementUnit = new MeasurementUnit(name);
         try (RuleUnitInstance<MeasurementUnit> instance = RuleUnitProvider.get().createRuleUnitInstance(measurementUnit)) {
                    // fire rules
          }
 }

One concern though:

I noticed that if NamedRuleUnitData is not implemented, then the RuleUnit's class is put into a HashMap and it becomes impossible to create a new version because the HashMap keeps returning the same instance:
(Snippet from https://github.com/kiegroup/drools/blob/main/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/RuleUnitProviderImpl.java)
 @Override
    public <T extends RuleUnitData> RuleUnit<T> getRuleUnit(T ruleUnitData) {
        String ruleUnitName = getRuleUnitName(ruleUnitData);
        RuleUnit<T> ruleUnit = ruleUnitMap.get(ruleUnitName);
        if (ruleUnit != null) {
            return ruleUnit;
        }
        ruleUnitMap.putAll(generateRuleUnit(ruleUnitData));
        return ruleUnitMap.get(ruleUnitName);
    }

I am concerned though that this would create a memory leak? It appears that there isn't a WeakReference to the RuleUnit used in that ruleUnitMap.  There is a ServiceLoader used to load the rule unit class, so perhaps that is destroyed and allows the no-longer-used RuleUnitData to be freed. I didn't follow the code enough to determine if that is true though.

Thanks for you help!
 -- Adam

Toshiya Kobayashi

unread,
Apr 12, 2023, 3:12:11 AM4/12/23
to Drools Usage
Hi Adam,

While KieContainer was designed to run multiple rule versions in single JVM, RuleUnit is more likely designed for microservice use cases (e.g. kogito). In microservice use cases, when you want to update your rules, you may simply build a new project (incrementing maven GAV version) and deploy it as a new pod. Does it meet your use case?

Regards,
Toshiya

2023年3月30日木曜日 23:52:03 UTC+9 abelm...@gmail.com:

Adam Belmont

unread,
Apr 19, 2023, 3:39:15 PM4/19/23
to Drools Usage
Thanks for the reply Toshiya!

My rules are used to implemention promotional pricing discounts. There could be many (1000s) of these discount rules running across various tenants. While they are still be created / tested there would be many updates so I'm not sure it's practical to be continuously rebuilding and deploying pods? I'm using the org.drools.ruleunits.dsl.* package to add filters, conditions etc based upon what the discount condition are. However, it is a microservice that can start up quickly in a second or two. I'm running this inside quarkus, but not using kogito, just "vanilla" quarkus and java to load up drools. 

Incrementing the RuleUnit name is working now, but I'll have to track the old versions and restart the container at some internal since they will build up over time.

If there were a way to say "please rebuild this rule unit from scratch" in the api that would be super helpful.
// Rebuilding a Rule Unit
boolean rebuildRuleUnit = true;
RuleUnitProvider.get().createRuleUnitInstance(myRuleUnit,rebuildRuleUnit);

I suppose another alternative is that I build rules that are mostly static, where the filtering, etc.. is just done in java code. But that seems to defeat the purpose of using drools (I'm fairly new so I may be looking at the problem in a fundamentally wrong way).
For example, somethign like this wich puts all the work into the order fact class may work, but then it seems like I'd be re-imlementing ,in a very poor way, much of the rule engine logic inside those fact classes.
 rulesFactory.rule("MyVeryStaticRule")
.on(orders) .filter(order ->  order.discountCount(), GREATER_THAN, 0)   ) .execute (results, (r, order) -> r.add(order.discounts()));   
 

 Thanks again for your help,
 -- Adam

Toshiya Kobayashi

unread,
Apr 23, 2023, 11:24:19 PM4/23/23
to Drools Usage
Hi Adam,


> I'm running this inside quarkus, but not using kogito, just "vanilla" quarkus and java to load up drools.

vanilla quarkus + drools is fine :)


> While they are still be created / tested there would be many updates so I'm not sure it's practical to be continuously rebuilding and deploying pods?

Rule updates are equivalent to application logic updates, so it makes sense to rebuild and deploy pods, so you have control of versioning as well. Microservice environments should have good support for such CI/CD, I think.

But anyway, I follow your points.


> If there were a way to say "please rebuild this rule unit from scratch" in the api that would be super helpful.
> // Rebuilding a Rule Unit
> boolean rebuildRuleUnit = true;
> RuleUnitProvider.get().createRuleUnitInstance(myRuleUnit,rebuildRuleUnit);

It looks like a reasonable feature to me. Thank you for the suggestion. I filed a JIRA https://issues.redhat.com/browse/DROOLS-7416 and check with my colleagues.


> I suppose another alternative is that I build rules that are mostly static, where the filtering, etc.. is just done in java code. But that seems to defeat the purpose of using drools (I'm fairly new so I may be looking at the problem in a fundamentally wrong way).
> For example, somethign like this wich puts all the work into the order fact class may work, but then it seems like I'd be re-imlementing ,in a very poor way, much of the rule engine logic inside those fact classes.
>  rulesFactory.rule("MyVeryStaticRule")
>                     .on(orders)
>                     .filter(order ->  order.discountCount(), GREATER_THAN, 0)   )
>                     .execute (results, (r, order) -> r.add(order.discounts()));  

It's not a very unusual case. Some users encapsulate some logic into a java method. However, the approach basically depends on what you want to maintain as rules. So if you think it is poorly re-implementing rules into java class, then your feeling is right and it should be avoided.

Regards,
Toshiya

2023年4月20日木曜日 4:39:15 UTC+9 abelm...@gmail.com:

Adam Belmont

unread,
May 9, 2023, 9:46:44 AM5/9/23
to Drools Usage
Thanks very much for your suggestions and for filing the enhancement request Toshiya!
 -- Adam

Toshiya Kobayashi

unread,
Aug 1, 2023, 4:13:51 AM8/1/23
to Drools Usage
Hi Adam,

We have resolved https://issues.redhat.com/browse/DROOLS-7416 which will be available in drools 8.43.0.Final targeting around 3 weeks after.

It implements `invalidateRuleUnits`  method. It's not "rebuild", but it will allow you to write invalidateRuleUnits and create the RuleUnit again, so it would be the same as "rebuild".

Thanks!
Toshiya

2023年5月9日火曜日 22:46:44 UTC+9 abelm...@gmail.com:

Richa Singh

unread,
Aug 1, 2023, 9:32:28 AM8/1/23
to Drools Usage
Hi Toshiya,

These are very interesting insights. In KieServer environment we were building kiecontainer per tenant at runtime and deploying using kieserver's rest api. 
Tenant 1 -> rule2 -> Container1 -> rest url 1
Tenant2 -> rule2 -> Container2 -> rest url 2

This allows us to handle data privacy/security concerns.

What will be an equivalent way using Kogito?  Would we create docker/pod per tenant and deploy on Kubernetes cluster.
Is there another way?

Thanks
Richa

Toshiya Kobayashi

unread,
Aug 1, 2023, 10:56:01 PM8/1/23
to Drools Usage
Hi Richa,


> Would we create docker/pod per tenant and deploy on Kubernetes cluster.

Yes, that's the expected architecture. Cloud environment should have good support for such deployment.

Regards,
Toshiya

2023年8月1日火曜日 22:32:28 UTC+9 rri...@gmail.com:

Adam Belmont

unread,
Aug 2, 2023, 10:22:49 AM8/2/23
to Drools Usage
That's great news! Thank you so much Toshiya. That seems like it would work out well for my use case. I'm looking forward to giving it a try.

 -- Adam

Adam Belmont

unread,
Aug 26, 2023, 2:43:11 PM8/26/23
to Drools Usage
Quick update Toshiya. 
I moved to Drools 8.43.0.Final and the new RuleUnitProvider.get().invalidateRuleUnits(Class) is working exactly as a hoped. Previous rule unit is discarded, and I can load updated versions.

Thank you very much for your help!
 -- Adam

Toshiya Kobayashi

unread,
Aug 27, 2023, 9:26:23 PM8/27/23
to Drools Usage
Thank you for the feedback, Adam!

Toshiya

2023年8月27日日曜日 3:43:11 UTC+9 abelm...@gmail.com:

Richa Singh

unread,
Aug 28, 2023, 8:23:39 PM8/28/23
to Drools Usage
Thats great Adam. I am interested to know little more about your implementation.

We have the following requirements:
1. Multitenancy
2. Authoring and deploying in real time by users
3. Ability to publish the rules while data is already being evaluated.



All the examples with Qarkus/Kogito are for static rules so was wondering what approaches people are taking

Thanks
Richa

leven chen

unread,
Aug 28, 2023, 10:23:40 PM8/28/23
to drools...@googlegroups.com
Hi 

Maybe my implementation will help you.

In Qarkus/Kogito the recommendation is to use a cloud-native approach to achieve multi-tenancy, so you need K8S etc. are needed for service orchestration.

I implemented dynamic rules and multitenancy based on Drools 8.0 in Java application. No kogito selected.



Richa Singh <rri...@gmail.com> 于2023年8月29日周二 08:23写道:
--
You received this message because you are subscribed to the Google Groups "Drools Usage" group.
To unsubscribe from this group and stop receiving emails from it, send an email to drools-usage...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/drools-usage/16499820-af5e-43f6-a0ff-964e455085c3n%40googlegroups.com.

Adam Belmont

unread,
Aug 29, 2023, 3:44:52 PM8/29/23
to Drools Usage
Hi,
I'm also using Drools 8 without kogito. I happen to be using Quarkus, but the same code runs standalone in unit tests so quarkus isn't required.

1. Multitenant: Requests to my quarkus server are multi tenant. JWT token allows me to know which tenant a given request is for.  Rules are stored as a structure in the db with a tenantId property. 

2. Real Time Authoring:  I'm using the Rule Unit DSL api to create rules at runtime. https://docs.drools.org/8.43.0.Final/drools-docs/drools/KIE/index.html#rule-unit-dsl_packaging-deploying. At startup, or at runtime rules are generated from the db structure inside the RuleUnit's 'defineRules(RulesFactory)' method. With the new feature Toshiya added that allows invalidating a RuleUnit, it means that if a given rule unit has already been loaded into the jvm, I can force a refresh and rebuild if a rule changes. No need to restart the jvm.

3. Caveat: The rule units themselves are not tenant specific. So if any tenant changes a rule from a rule unit, then all rules for that unit would be rebuilt for all tenants running in that jvm. It's been fast enough so far, but this is probably an area for better optimization.

4. Live Publishing: Requests to quarkus for runtime rule evations are done through Redis pub sub, so there aren't any direct http requests for rule evaluation. The http requests are only for editing the rule structure, and triggering the rebuild. There will be a short delay (currently 1-2 seconds) in message response to a given jvm during rebuild. Kafaka consumer groups are perhaps better here so that messages don't even get routed to nodes that are rebuilding.. But that's a different topic :)



fwiw here's pom.xml snippet for the standalone project that builds and tests the library, this is later used in a quarkus project.

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <drools-version>8.43.0.Final</drools-version>
    <slf4j-version>1.7.30</slf4j-version>
    <junit-version>5.9.2</junit-version>
    <assertj-version>3.24.2</assertj-version>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-bom</artifactId>
        <type>pom</type>
        <version>${drools-version}</version>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>

    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-ruleunits-engine</artifactId>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-model-compiler</artifactId>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-xml-support</artifactId>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-mvel</artifactId>
    </dependency>

...
<plugin>
        <groupId>org.kie</groupId>
        <artifactId>kie-maven-plugin</artifactId>
        <version>${drools-version}</version>
        <extensions>true</extensions>
      </plugin>
    </plugins>

  </build>

Richa Singh

unread,
Aug 29, 2023, 6:41:12 PM8/29/23
to Drools Usage
Thanks, Both the examples are helpful.

Since you are using drools-mvel, I am assuming you are creating a drl file

Thanks
Richa

Adam Belmont

unread,
Aug 30, 2023, 10:37:54 AM8/30/23
to Drools Usage
Good catch, I have a .drl file in that project. But it's unrelated to the use case above. All the rules for what I was talking about are created with RuleUnits and the Java api for building rules.
-- Adam

Reply all
Reply to author
Forward
Message has been deleted
0 new messages