【Drools】Rule Unitについて学ぶ

本記事では、Droolsの「Rule Unit」について学んだことを記載していきます。

[Rule Unitとは](#rule-unitとは)
[サンプルコード](#サンプルコード)
[Rule Unitのメリット](#rule-unitのメリット)

## Rule Unitとは

Drools version 8 で新たに追加された、ルールを実装・実行する仕組みです。

過去記事では、クラスパスに配置したDRLファイルからKieContainerを生成して、ルールを実行する方法を紹介していました。

https://qiita.com/yagii/items/5c1ee8781dc16252d2a3

Drools version 7 まではこの方法が一般的でしたが、version 8 からはRule Unitを使う方法が推奨されており、公式ドキュメントのチュートリアルもその方法に則ったものになっています。

Rule Unitは、アプリケーションで実行するRuleと、DataSourceという二つの要素からなります。

```mermaid
graph

subgraph 0_[Rule Unit]
    subgraph DataSource
        1_[DataStream]
        2_[DataStore/SingletonStore] 
    end    
    3_[Rule]
end

user --> DataSource
3_ --> DataSource
```

※ 矢印はデータの参照や更新

構成要素のうちのRuleは、これまでと同様DRLファイルなどで定義されたルールを指します。

DataSourceは、ルールが参照するデータを明示的に定義して、ルール実行時に保存できるようにするための要素です。
ルールの実行側はDataSource内のデータを操作することで、Rule Unitとやり取りすることができます。
DataSourceとしては、以下3種類が用意されています。

- DataStream
    - データを追加するため専用のDataSourceです
    - 後述のDataStoreのように、更新や削除はできないようです
- DataStore
    - データの追加や、追加したデータの更新・削除を行うことのできるDataSourceです
- SingletonStore
    - DataStoreと同じようなものですが、追加できるデータは1件のみのようです
    - 公式ドキュメントのRule Unitの概念図にも登場しないですが、DataStoreの一種という扱いなのかなと思います

各要素について詳細は、公式ドキュメントも参照してください。

https://docs.drools.org/8.41.0.Final/drools-docs/drools/KIE/index.html#rule-unit-api_packaging-deploying

## サンプルコード

Rule Unitの仕組みを使ってどのようにルールを作成・実行するかを見ていきます。
紹介するサンプルコードの全量は[GitHub](https://github.com/Hami1ton/study_brms/tree/main/drools/rule_unit/rule_unit_drl_sample)にアップしています。
題材は過去記事と同様、顧客の年齢をもとにドリンクを決定するルールとなっています。

### データクラス作成

ルールで扱うデータクラスを用意します。

```Drink.java
public class Drink {

    private String name;

    private int charge;
...
```

```Person.java
public class Person {

    private String name;

    private int age;
...
```

### Rule Unitの作成

RuleUnitDataクラスをimplementして、Rule Unitクラスを作成します。
本クラスは以下のフィールドをもちます。
- persons
    - Personオブジェクトを保持するためのDataStore
    - Personオブジェクトは、ドリンクを決定するためのルールの入力値となります
- drinkList
    - ルールで決定したDrinkオブジェクトを保持するためのリスト

```DrinkRuleUnit.java
public class DrinkRuleUnit implements RuleUnitData {
    
    private DataStore<Person> persons;

    private List<Drink> drinkList = new ArrayList<>();

    public DrinkRuleUnit() {
        this(DataSource.createStore());
    }

    public DrinkRuleUnit(DataStore<Person> persons) {
        this.persons = persons;
    }

    public void setPersons(DataStore<Person> persons) {
        this.persons = persons;
    }

    public DataStore<Person> getPersons() {
        return persons;
    }

    public List<Drink> getDrinkList() {
        return drinkList;
    }
   
}
```

### ルールの作成

ルールをDRLファイルに記述します。

```Sample.drl
package org.example.ruleunit.drlsample;
unit DrinkRuleUnit;

rule "Child"
    when
        /persons[ age < 20 ]
    then
        drinkList.add(new Drink("Orange Juice", 100));
end

rule "Adult"
    when
        /persons[ age >= 20 ]
    then
        drinkList.add(new Drink("Beer", 200));
end
```

ここで
```
unit DrinkRuleUnit;
```
のようにRule Unitのクラスを指定することで、DRLファイルと作成したRule Unitとの紐づけを行っています。

また、「20歳未満のPersonオブジェクトが存在する場合」という条件を以下のように記述しています。
```
    when
        /persons[ age < 20 ]
    then
```
ここではOOPathという構文を使用して、DrinkRuleUnit内のpersonsフィールドを照合しています。
Drools version 8 ドキュメント内のDRLは、OOPathの構文を使ったものが多いため、それに倣っています。
※ 公式ドキュメント [New and traditional syntax](https://docs.drools.org/8.41.0.Final/drools-docs/drools/language-reference/index.html#new-and-traditional-syntax_drl-rules)

以上でルールの実装は完了です。
**Drools version 7 ではkmodule.xmlをリソースパスに追加していましたが、Rule Unitでは不要です。**

### ルール実行用のテストクラス作成

ルール実行のコードは以下のように書くことができます。

```SampleTest.java
    @Test
    public void test_ドリンクの決定() {
        DrinkRuleUnit drinkRuleUnit = new DrinkRuleUnit();
        RuleUnitInstance<DrinkRuleUnit> instance = RuleUnitProvider.get().createRuleUnitInstance(drinkRuleUnit);

        var person = new Person("Taro", 20);
        drinkRuleUnit.getPersons().add(person);

        // execute rule 
        instance.fire();

        // assert
        var drink = drinkRuleUnit.getDrinkList().get(0);
        assertEquals("Beer", drink.getName());

        instance.close();
    }
```

本テストが問題なくパスするため、ルールがPersonオブジェクト(name="Taro", age=20)を受け取って、想定通りのDrink(Beer)を生成したことがわかります。

## Rule Unitのメリット

公式ブログでは、以下のようにRule Unitのコンセプトが紹介されています。

```
a rule unit encapsulates the unit of execution for rules and the data against which the rules will be matched.
```
https://blog.kie.org/2022/10/drools-8-final-toward-a-modular-and-cloud-native-rule-engine.html

大規模なアプリケーションになると、ルールを記述したDRLファイルと、ルールが参照するデータクラスが肥大化していくことが予想されます。
Rule Unitという単位でルールとデータをカプセル化することで管理しやすくし、アプリケーションの保守性を高めるメリットがあると思います。

実際に今回サンプルコードを書いてみて、RuleUnitDataとDataSourceにより、ルールを呼び出すJavaコードとルールとの間のインターフェースが明解になっていると感じました。