本記事では、Droolsの「Rule Unit」について学んだことを記載していきます。
Rule Unitとは
サンプルコード
Rule Unitのメリット
Rule Unitとは
Drools version 8 で新たに追加された、ルールを実装・実行する仕組みです。
過去記事では、クラスパスに配置したDRLファイルからKieContainerを生成して、ルールを実行する方法を紹介していました。
Drools version 7 まではこの方法が一般的でしたが、version 8 からはRule Unitを使う方法が推奨されており、公式ドキュメントのチュートリアルもその方法に則ったものになっています。
Rule Unitは、アプリケーションで実行するRuleと、DataSourceという二つの要素からなります。
※ 矢印はデータの参照や更新
構成要素のうちのRuleは、これまでと同様DRLファイルなどで定義されたルールを指します。
DataSourceは、ルールが参照するデータを明示的に定義して、ルール実行時に保存できるようにするための要素です。
ルールの実行側はDataSource内のデータを操作することで、Rule Unitとやり取りすることができます。
DataSourceとしては、以下3種類が用意されています。
- DataStream
- データを追加するため専用のDataSourceです
- 後述のDataStoreのように、更新や削除はできないようです
- DataStore
- データの追加や、追加したデータの更新・削除を行うことのできるDataSourceです
- SingletonStore
- DataStoreと同じようなものですが、追加できるデータは1件のみのようです
- 公式ドキュメントのRule Unitの概念図にも登場しないですが、DataStoreの一種という扱いなのかなと思います
各要素について詳細は、公式ドキュメントも参照してください。
サンプルコード
Rule Unitの仕組みを使ってどのようにルールを作成・実行するかを見ていきます。
紹介するサンプルコードの全量はGitHubにアップしています。
題材は過去記事と同様、顧客の年齢をもとにドリンクを決定するルールとなっています。
データクラス作成
ルールで扱うデータクラスを用意します。
public class Drink {
private String name;
private int charge;
...
public class Person {
private String name;
private int age;
...
Rule Unitの作成
RuleUnitDataクラスをimplementして、Rule Unitクラスを作成します。
本クラスは以下のフィールドをもちます。
- persons
- Personオブジェクトを保持するためのDataStore
- Personオブジェクトは、ドリンクを決定するためのルールの入力値となります
- drinkList
- ルールで決定したDrinkオブジェクトを保持するためのリスト
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ファイルに記述します。
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
以上でルールの実装は完了です。
Drools version 7 ではkmodule.xmlをリソースパスに追加していましたが、Rule Unitでは不要です。
ルール実行用のテストクラス作成
ルール実行のコードは以下のように書くことができます。
@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.
大規模なアプリケーションになると、ルールを記述したDRLファイルと、ルールが参照するデータクラスが肥大化していくことが予想されます。
Rule Unitという単位でルールとデータをカプセル化することで管理しやすくし、アプリケーションの保守性を高めるメリットがあると思います。
実際に今回サンプルコードを書いてみて、RuleUnitDataとDataSourceにより、ルールを呼び出すJavaコードとルールとの間のインターフェースが明解になっていると感じました。