今回は、Drools のルール実行制御機能について紹介していきます。
ルールの実行制御
次のようなルールがある状況で、Rule_A ⇒ Rule_C ⇒ Rule_B の順番で動作することを保証したいとします。
rule "Rule_A"
when 〇〇の条件をみたすとき
then XXを行う
end
rule "Rule_B"
when 〇〇の条件をみたすとき
then XXを行う
end
rule "Rule_C"
when 〇〇の条件をみたすとき
then XXを行う
end
ルールの動作順を制御するために、Drools には表のような機能が用意されています。
| 機能名 | 概要 |
|---|---|
| Agenda group |
agenda-group という属性でルールをグループ分けし、実行順を制御する仕組み |
| Ruleflow group |
ruleflow-group という属性でルールをグループ分けし、実行順を制御する仕組み |
| Declarative agenda | 「どのルールを動かすか」という判断自体もルールで記述する仕組み(実験的機能) |
Agenda group, Ruleflow group について
最近の Drools (v8〜10) では、ルールのグループ分けは Rule Unit で行うことが推奨されています。1
ただ、長年メンテナンスされてきた機能を使いたいユーザもいると考え、本記事ではあえてレガシーな機能を検証します。
サンプルコード
ここからは、各実行制御機能のサンプル実装を一部抜粋する形で紹介していきます。
コード全量は GitHub にアップしており、それぞれの機能ごとにプロジェクトを分けています。
開発環境情報など
・openjdk 17
・Apache Maven 3.9.6
・Drools 10.0.0
検証用オブジェクト
ルールが意図した順序で動いているかを確認するため、以下の SampleData クラスを使用します。
「実行されたルール名リスト」、「最後に実行されたルール名」を保持することで制御順をトレスします。
Junit テストコードで値が想定通りかを確認します。
public class SampleData {
// これまで実行されたルール名のリスト
private List<String> executedRules = new ArrayList<>();
// 最後に実行されたルール名
private String latestRule = "";
public void addRuleName(String ruleName) {
this.executedRules.add(ruleName);
}
public void setLatestRule(String ruleName) {
this.latestRule = ruleName;
}
// 他メソッドは省略
Agenda group
agenda-group 属性でルールをグループ分けすることができます。
以下が DRL で、Rule_A, Rule_B, Rule_C という3つのルールを定義し、それぞれ Phase_1~3の 3 グループに分けています。
rule "Rule_A"
agenda-group "Phase_1"
when
$data : SampleData()
then
$data.addRuleName("Rule_A");
$data.setLatestRule("Rule_A");
System.out.println($data);
end
rule "Rule_B"
agenda-group "Phase_2"
when
$data : SampleData()
then
$data.addRuleName("Rule_B");
$data.setLatestRule("Rule_B");
System.out.println($data);
end
rule "Rule_C"
agenda-group "Phase_3"
when
$data : SampleData()
then
$data.addRuleName("Rule_C");
$data.setLatestRule("Rule_C");
System.out.println($data);
end
Java コード上でグループ名を指定し活性化することで、当該グループ内のルールのみ動作させることができます。
以下テストコードで、Rule_A⇒Rule_C⇒Rule_B の順に実行されたことを確認できます。
@Test
public void test_実行順検証() {
var data = new SampleData();
kieSession.insert(data);
// Phase_1
kieSession.getAgenda().getAgendaGroup("Phase_1").setFocus();
kieSession.fireAllRules();
// Phase_3
kieSession.getAgenda().getAgendaGroup("Phase_3").setFocus();
kieSession.fireAllRules();
// Phase_2
kieSession.getAgenda().getAgendaGroup("Phase_2").setFocus();
kieSession.fireAllRules();
// assert
assertEquals(Arrays.asList("Rule_A", "Rule_C", "Rule_B"), data.getExecutedRules());
assertEquals("Rule_B", data.getLatestRule());
}
Ruleflow group
Agenda group と同様、DRL に属性( ruleflow-group )を指定してルールをグループ分けする仕組みです。
Ruleflow group はもともと、DRL を BPMN2 ワークフローと組み合わせ、実行順を制御する機能でした。
BPMN モデルを記述した XML ファイルをプロジェクトに追加して、jbpm-bpmn2というライブラリで Drools と連携する仕掛けのようです。
ただし、公式のIssueをおったところ、この方式は Drools v10 ではサポートされてないようでした。
Agenda group 同様に Java コードで活性化するとしたら、下記のようになります。
rule "Rule_A"
ruleflow-group "Phase_1"
when
$data : SampleData()
then
$data.addRuleName("Rule_A");
$data.setLatestRule("Rule_A");
System.out.println($data);
end
// Rule_B, Rule_C も同様
@Test
public void test_実行順検証() {
var data = new SampleData();
kieSession.insert(data);
// Phase_1
((InternalAgenda) kieSession.getAgenda()).activateRuleFlowGroup("Phase_1");
kieSession.fireAllRules();
// Phase_3
((InternalAgenda) kieSession.getAgenda()).activateRuleFlowGroup("Phase_3");
kieSession.fireAllRules();
// Phase_2
((InternalAgenda) kieSession.getAgenda()).activateRuleFlowGroup("Phase_2");
kieSession.fireAllRules();
// assert
assertEquals(Arrays.asList("Rule_A", "Rule_C", "Rule_B"), data.getExecutedRules());
assertEquals("Rule_B", data.getLatestRule());
}
キャストで内部APIを呼ぶ特殊な方式になるため、おとなしく Agenda group を使ったほうがよさそうです..。
Declarative agenda
他ルールの動作をブロック、もしくはブロック解除するルール(※)を定義することで、実行制御する仕組みです。
(※) 以降、「制御用ルール」と呼びます
公式ドキュメント3で実験的機能として紹介されており、将来的に大きく変更される可能性があります。
デフォルトでは本機能はオフになっているため、kmodule.xmlで有効化する必要があります。
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="DeclarativeKBase" declarativeAgenda="enabled">
<ksession name="DeclarativeKSession"/>
</kbase>
</kmodule>
以下が DRL です。
rule "Rule_A" @Eager @phase("Phase_1")
lock-on-active true
when
$data : SampleData()
then
$data.addRuleName("Rule_A");
modify ($data) { setLatestRule("Rule_A") };
System.out.println($data);
end
rule "Rule_B" @Eager @phase("Phase_2")
lock-on-active true
when
$data : SampleData()
then
$data.addRuleName("Rule_B");
modify ($data) { setLatestRule("Rule_B") };
System.out.println($data);
end
rule "Rule_C" @Eager @phase("Phase_3")
lock-on-active true
when
$data : SampleData()
then
$data.addRuleName("Rule_C");
modify ($data) { setLatestRule("Rule_C") };
System.out.println($data);
end
rule "Phase_2 ルールをブロック" @Direct
when
$m: Match(rule.metaData["phase"] == "Phase_2")
$d: SampleData(latestRule == "Rule_A")
then
System.out.println("ブロック");
kcontext.blockMatch($m);
end
rule "Phase_2 ルールをブロック解除" @Direct
when
$m: Match(rule.metaData["phase"] == "Phase_2")
$d: SampleData(latestRule == "Rule_C")
then
System.out.println("ブロック解除");
kcontext.unblockAllMatches($m);
end
DRL では下記のアノテーションを使用しています。
| annotation | 説明 |
|---|---|
@Eager |
実行順を制御される可能性のあるルールに付与 |
@Direct |
制御用ルールに付与 条件をみたした時点で即座に発動することを許可する |
@phase |
ルールにメタデータを付与し、制御用ルールの条件で使用可能 名前は「phase」以外でもよい |
@Direct を付けた 2 ルールが制御用ルールです。
Phase_2 メタデータが付与されたルールの動きを監視し、Rule_C が完了するまでは実行をブロックします。
以下が動作確認用のテストコードです。
@Test
public void test_実行順検証() {
var data = new SampleData();
kieSession.insert(data);
// execute
kieSession.fireAllRules();
// assert
assertEquals(Arrays.asList("Rule_A", "Rule_C", "Rule_B"), data.getExecutedRules());
assertEquals("Rule_B", data.getLatestRule());
}
終わりに
Drools ルール実行制御機能を 3 つ紹介しました。
個人的には Agenda group が最もシンプルで使いやすそうです。
-
公式ドキュメント Legacy rule attributes を参照 ↩
-
Business Process Model and Notation ↩
-
公式ドキュメント Declarative agenda を参照 ↩