83.7. DRL ルールセットのルールユニット
ルールユニットは、データソース、グローバル変数、および DRL ルールのグループで、特定の目的に向けて互いに機能し合います。ルールユニットを使用して、ルールセットを小さなユニットに分割し、それらのユニットにさまざまなデータソースをバインドしてから、個別のユニットを実行します。ルールユニットは、実行制御用のルールアジェンダグループまたはアクティブ化グループなどの、ルールをグループ化する DRL 属性に代わるものとして、強化されています。
ルールユニットは、ルールの実行を調整することで、あるルールユニットが完全に実行されると別のルールユニットの開始をトリガーする場合などに便利です。たとえば、データ強化用の一連のルール、そのデータを処理する別の一連のルール、および処理されたデータを抽出して出力する別の一連のルールがあるとします。これらのルールセットを 3 つの異なるルールユニットに追加する場合は、これらのルールユニットを調整することで、1 つ目のユニットが完全に実行すると 2 つ目のユニットの開始をトリガーし、2 つ目のユニットが完全に実行すると 3 つ目のユニットの開始をトリガーすることができます。
ルールユニットを定義するには、以下の例に示すように RuleUnit
インターフェイスを実装します。
ルールユニットクラスの例
package org.mypackage.myunit; public static class AdultUnit implements RuleUnit { private int adultAge; private DataSource<Person> persons; public AdultUnit( ) { } public AdultUnit( DataSource<Person> persons, int age ) { this.persons = persons; this.age = age; } // A data source of `Persons` in this rule unit: public DataSource<Person> getPersons() { return persons; } // A global variable in this rule unit: public int getAdultAge() { return adultAge; } // Life-cycle methods: @Override public void onStart() { System.out.println("AdultUnit started."); } @Override public void onEnd() { System.out.println("AdultUnit ended."); } }
この例では、persons
はタイプ Person
のファクトのソースです。ルールユニットのデータソースは、指定のルールユニットで処理されるデータのソースで、デシジョンエンジンがルールユニットの評価に使用するエントリーポイントを表します。adultAge
グローバル変数は、このルールユニットに属するすべてのルールからアクセスできます。最後の 2 つのメソッドは、ルールユニットのライフサイクルの一部で、デシジョンエンジンによって呼び出されます。
デシジョンエンジンは、以下のようなルールユニットの任意のライフサイクルメソッドをサポートします。
メソッド | 呼び出されるタイミング |
---|---|
| ルールユニット実行開始時 |
| ルールユニット実行終了時 |
|
ルールユニット実行の一時停止時 ( |
|
ルールユニット実行の再開時 ( |
| ルールユニットにおけるルールの結果が異なるルールユニットの実行をトリガー |
ルールユニットに、ルールを 1 つ以上追加することができます。デフォルトでは、DRL ファイルのすべてのルールは、DRL ファイル名の命名規則に従うルールユニットに自動的に関連付けられます。DRL ファイルが同じパッケージにあり、RuleUnit
インターフェイスを実装するクラスと同じ名前を持つ場合、その DRL ファイルのすべてのルールは、そのルールユニットに暗黙的に属します。たとえば、org.mypackage.myunit
パッケージの AdultUnit.drl
ファイルにあるすべてのルールは、自動的にルールユニット org.mypackage.myunit.AdultUnit
の一部となります。
この命名規則をオーバーライドし、DRL ファイル内のルールが属するルールユニットを明示的に宣言するには、DRL ファイル内でキーワード unit
を使用します。unit
宣言は、すぐに package 宣言に従い、DRL ファイルのルールが一部となっているパッケージ内のクラス名を含む必要があります。
DRL ファイルのルールユニット宣言の例
package org.mypackage.myunit unit AdultUnit rule Adult when $p : Person(age >= adultAge) from persons then System.out.println($p.getName() + " is adult and greater than " + adultAge); end
同じ KIE ベースで、ルールユニットありのルールとルールユニットなしのルールを混在させないでください。KIE ベースで 2 つのルールのパラダイムを混在させると、コンパイルエラーが発生します。
以下の例のように OOPath 表記を使用して、より便利な方法で同じパターンを書き換えることもできます。
OOPath 表記を使用する DRL ファイルのルールユニット宣言の例
package org.mypackage.myunit unit AdultUnit rule Adult when $p : /persons[age >= adultAge] then System.out.println($p.getName() + " is adult and greater than " + adultAge); end
OOPath は、DRL ルールの条件の制約でオブジェクトのグラフを参照するために設計された XPath のオブジェクト指向構文の拡張です。OOPath は、コレクションおよびフィルター制約を処理する間に XPath からのコンパクト表記を使用して関連要素を移動します。また、OOPath は特にオブジェクトグラフの場合に役に立ちます。
この例では、ルール条件で一致するファクトはすべて、ルールユニットクラスの DataSource
定義で定義される persons
のデータソースから取得されます。ルール条件およびアクションは、グローバル変数が DRL ファイルレベルで定義されるのと同じ方法で adultAge
変数を使用します。
KIE ベースに定義されたルールユニットを 1 つ以上実行するには、KIE ベースにバインドされている新規の RuleUnitExecutor
クラスを作成し、関連するデータソースからルールユニットを作成して、ルールユニットエグゼキューターを実行します。
ルールユニット実行の例
// Create a `RuleUnitExecutor` class and bind it to the KIE base: KieBase kbase = kieContainer.getKieBase(); RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase ); // Create the `AdultUnit` rule unit using the `persons` data source and run the executor: RuleUnit adultUnit = new AdultUnit(persons, 18); executor.run( adultUnit );
ルールは RuleUnitExecutor
クラスにより実行されます。RuleUnitExecutor
クラスは KIE セッションを作成し、必要な DataSource
オブジェクトをこれらのセッションに追加してから、run()
メソッドへのパラメーターとして渡される RuleUnit
をもとに、ルールを実行します。
例の実行コードは、関連する Person
ファクトが persons
データソースに挿入されると、以下の出力を生成します。
ルールユニット実行出力の例
org.mypackage.myunit.AdultUnit started. Jane is adult and greater than 18 John is adult and greater than 18 org.mypackage.myunit.AdultUnit ended.
ルールユニットインスタンスを明示的に作成するのではなく、エグゼキューターにルールユニット変数を登録し、実行するルールユニットクラスをエグゼキューターに渡すと、エグゼキューターがルールユニットのインスタンスを作成します。続いて、ルールユニットを実行する前に DataSource
定義および他の変数を設定できます。
登録変数を含む別のルールユニット実行オプション
executor.bindVariable( "persons", persons ); .bindVariable( "adultAge", 18 ); executor.run( AdultUnit.class );
RuleUnitExecutor.bindVariable()
メソッドに渡す名前は、実行時に、同じ名前のルールユニットクラスのフィールドに変数をバインドするために使用されます。前述の例では、RuleUnitExecutor
は、新しいルールユニットに "persons"
の名前にバインドされているデータソースを挿入します。また、AdultUnit
クラス内の対応する名前のフィールドに、文字列 "adultAge"
にバインドされている値 18
を挿入します。
このデフォルトの変数バインディング動作をオーバーライドするには、@UnitVar
アノテーションを使用してルールユニットクラスの各フィールドに対して論理バインディング名を明示的に定義します。たとえば、以下のクラスのフィールドバインディングは、代替名で再度定義されます。
@UnitVar
を使用した変数バインディング名を変更するコード例
package org.mypackage.myunit; public static class AdultUnit implements RuleUnit { @UnitVar("minAge") private int adultAge = 18; @UnitVar("data") private DataSource<Person> persons; }
次に、これらの代替名を使用して、変数をエグゼキューターにバインドし、ルールユニットを実行できます。
変更した変数名を使用したルールユニット実行の例
executor.bindVariable( "data", persons ); .bindVariable( "minAge", 18 ); executor.run( AdultUnit.class );
ルールユニットは、run()
メソッド (KIE セッションで fireAllRules()
を呼び出す場合と同じ) を使用して パッシブモード で、または runUntilHalt()
メソッド (KIE セッションで fireUntilHalt()
を呼び出す場合と同じ) を使用して アクティブモード で実行できます。デフォルトでは、デシジョンエンジンは パッシブモード で実行し、ユーザーまたはアプリケーションが明示的に run()
(標準ルールでは fireAllRules()
) を呼び出す場合にのみルールユニットを評価します。ユーザーまたはアプリケーションがルールユニットに runUntilHalt()
(標準ルールでは fireAllRules()
) を呼び出す場合、デシジョンエンジンは アクティブモード で開始し、ユーザーまたはアプリケーションが明示的に halt()
を呼び出すまで、継続的にルールユニットを評価します。
runUntilHalt()
メソッドを使用する場合は、メインスレッドをブロックしないように、別の実行スレッド上でメソッドを呼び出します。
別のスレッド上の runUntilHalt()
を使用したルールユニットの実行例
new Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
83.7.1. ルールユニットのデータソース
ルールユニットのデータソースは、指定のルールユニットで処理されるデータのソースで、デシジョンエンジンがルールユニットの評価に使用するエントリーポイントを表します。ルールユニットは、ゼロまたは複数のデータソースを持つことができ、ルールユニット内で宣言された各 DataSource
の定義は、ルールユニットエグゼキューターへの異なるエントリーポイントに対応することができます。複数のルールユニットは、単一データソースを共有できます。ただし、各ルールユニットは、同じオブジェクトが挿入される異なるエントリーポイントを使用する必要があります。
以下の例で示すように、ルールユニットクラスの固定されたデータセットを使用して DataSource
定義を作成できます。
データソース定義の例
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ), new Person( "Jane", 44 ), new Person( "Sally", 4 ) );
データソースはルールユニットのエントリーポイントを表すため、ルールユニットでファクトを挿入、更新、または削除できます。
ルールユニットでファクトを挿入、更新、削除するコード例
// Insert a fact: Person john = new Person( "John", 42 ); FactHandle johnFh = persons.insert( john ); // Modify the fact and optionally specify modified properties (for property reactivity): john.setAge( 43 ); persons.update( johnFh, john, "age" ); // Delete the fact: persons.delete( johnFh );
83.7.2. ルールユニットの実行制御
一方のルールユニットの実行により、もう一方のルールユニットの開始がトリガーされるようにルールの実行を調整する必要がある場合に、ルールユニットは役に立ちます。
ルールユニットの実行制御を容易にするために、デシジョンエンジンは以下のルールユニットメソッドをサポートします。このメソッドは、DRL ルールアクションで使用して、ルールユニットの実行を調整することができます。
-
drools.run()
: 指定されたルールユニットクラスの実行をトリガーします。このメソッドでは、ルールユニットの実行を命令的に中断し、他の指定されたルールユニットを有効化します。 -
drools.guard()
: 関連付けられたルール条件が満たされるまで、指定されたルールユニットクラスが実行されないようにします (保護します)。このメソッドは、他の指定されたルールユニットの実行を宣言的にスケジュールします。デシジョンエンジンが、保護ルールの条件に対して少なくとも 1 つの一致をもたらす場合は、保護されたルールユニットが有効とみなされます。ルールユニットには、複数の保護ルールを含めることができます。
drools.run()
メソッドの例として、それぞれが指定されたルールユニットに属す以下の DRL ルールを検討してください。NotAdult
ルールは drools.run( AdultUnit.class )
メソッドを使用して AdultUnit
ルールユニットの実行をトリガーします。
drools.run()
を使用した制御された実行を含む DRL ルールの例
package org.mypackage.myunit unit AdultUnit rule Adult when Person(age >= 18, $name : name) from persons then System.out.println($name + " is adult"); end
package org.mypackage.myunit unit NotAdultUnit rule NotAdult when $p : Person(age < 18, $name : name) from persons then System.out.println($name + " is NOT adult"); modify($p) { setAge(18); } drools.run( AdultUnit.class ); end
この例では、これらのルールからビルドされた KIE ベースから作成された RuleUnitExecutor
クラスと、これにバインドされている persons
の DataSource
定義も使用します。
ルールエグゼキューターとデータソース定義の例
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase ); DataSource<Person> persons = executor.newDataSource( "persons", new Person( "John", 42 ), new Person( "Jane", 44 ), new Person( "Sally", 4 ) );
この例では、RuleUnitExecutor
クラスから DataSource
定義を直接作成し、これを単一ステートメントで "persons"
変数にバインドします。
例の実行コードは、関連する Person
ファクトが persons
データソースに挿入されると、以下の出力を生成します。
ルールユニット実行出力の例
Sally is NOT adult John is adult Jane is adult Sally is adult
NotAdult
ルールは、"Sally"
という人物の評価時に一致を検出します。この人物は 18 歳未満です。続いてこのルールは、この人物の年齢を 18
に変更し、drools.run( AdultUnit.class )
メソッドを使用して AdultUnit
ルールユニットの実行をトリガーします。AdultUnit
ルールユニットには、DataSource
定義の 3 人の persons
全員に対して実行可能となったルールが含まれています。
drools.guard()
メソッドの例として、以下の BoxOffice
クラスと BoxOfficeUnit
ルールユニットクラスを検討してください。
BoxOffice
クラスの例
public class BoxOffice { private boolean open; public BoxOffice( boolean open ) { this.open = open; } public boolean isOpen() { return open; } public void setOpen( boolean open ) { this.open = open; } }
BoxOfficeUnit
ルールユニットクラスの例
public class BoxOfficeUnit implements RuleUnit { private DataSource<BoxOffice> boxOffices; public DataSource<BoxOffice> getBoxOffices() { return boxOffices; } }
また、この例では、以下の TicketIssuerUnit
ルールユニットクラスを使用して、少なくとも 1 つのボックスオフィス (チケット売り場) が営業中である限り、ボックスオフィスでのイベントチケットの販売を続行します。このルールユニットは persons
および tickets
の DataSource
定義を使用します。
TicketIssuerUnit
ルールユニットクラスの例
public class TicketIssuerUnit implements RuleUnit { private DataSource<Person> persons; private DataSource<AdultTicket> tickets; private List<String> results; public TicketIssuerUnit() { } public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) { this.persons = persons; this.tickets = tickets; } public DataSource<Person> getPersons() { return persons; } public DataSource<AdultTicket> getTickets() { return tickets; } public List<String> getResults() { return results; } }
BoxOfficeUnit
ルールユニットには、DRL ルール BoxOfficeIsOpen
が含まれます。これは、drools.guard( TicketIssuerUnit.class )
メソッドを使用して、イベントチケットを配布する TicketIssuerUnit
ルールユニットの実行を保護します。以下に DRL ルールの例を示します。
drools.guard()
を使用した制御された実行を含む DRL ルールの例
package org.mypackage.myunit; unit TicketIssuerUnit; rule IssueAdultTicket when $p: /persons[ age >= 18 ] then tickets.insert(new AdultTicket($p)); end rule RegisterAdultTicket when $t: /tickets then results.add( $t.getPerson().getName() ); end
package org.mypackage.myunit; unit BoxOfficeUnit; rule BoxOfficeIsOpen when $box: /boxOffices[ open ] then drools.guard( TicketIssuerUnit.class ); end
この例では、少なくとも 1 つのボックスオフィスが open
である限り、保護された TicketIssuerUnit
ルールユニットが有効なため、イベントチケットは配布されます。open
状態のボックスオフィスがなくなると、保護された TicketIssuerUnit
ルールユニットは実行されなくなります。
以下のクラスの例は、より完全なボックスオフィスのシナリオを説明します。
ボックスオフィスシナリオのクラスの例
DataSource<Person> persons = executor.newDataSource( "persons" ); DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" ); DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" ); List<String> list = new ArrayList<>(); executor.bindVariable( "results", list ); // Two box offices are open: BoxOffice office1 = new BoxOffice(true); FactHandle officeFH1 = boxOffices.insert( office1 ); BoxOffice office2 = new BoxOffice(true); FactHandle officeFH2 = boxOffices.insert( office2 ); persons.insert(new Person("John", 40)); // Execute `BoxOfficeIsOpen` rule, run `TicketIssuerUnit` rule unit, and execute `RegisterAdultTicket` rule: executor.run(BoxOfficeUnit.class); assertEquals( 1, list.size() ); assertEquals( "John", list.get(0) ); list.clear(); persons.insert(new Person("Matteo", 30)); // Execute `RegisterAdultTicket` rule: executor.run(BoxOfficeUnit.class); assertEquals( 1, list.size() ); assertEquals( "Matteo", list.get(0) ); list.clear(); // One box office is closed, the other is open: office1.setOpen(false); boxOffices.update(officeFH1, office1); persons.insert(new Person("Mark", 35)); executor.run(BoxOfficeUnit.class); assertEquals( 1, list.size() ); assertEquals( "Mark", list.get(0) ); list.clear(); // All box offices are closed: office2.setOpen(false); boxOffices.update(officeFH2, office2); // Guarding rule is no longer true. persons.insert(new Person("Edson", 35)); executor.run(BoxOfficeUnit.class); // No execution assertEquals( 0, list.size() );
83.7.3. ルールユニットのアイデンティティーの競合
保護されたルールユニットを使用したルール実行のシナリオでは、1 つのルールが複数のルールユニットを保護することができます。同時に、複数のルールが 1 つのルールユニットを保護してから有効にすることもできます。このような 2 通りの保護シナリオでは、ルールユニットには、アイデンティティーの競合を避けるための明確に定義されたアイデンティティーが必要です。
デフォルトでは、ルールユニットのアイデンティティーはルールユニットクラス名で、RuleUnitExecutor
によりシングルトンクラスとして処理されます。この識別動作は、RuleUnit
インターフェイスの getUnitIdentity()
のデフォルトメソッドにエンコードされています。
RuleUnit
インターフェイスのデフォルトのアイデンティティーメソッド
default Identity getUnitIdentity() { return new Identity( getClass() ); }
場合によっては、ルールユニット間のアイデンティティーの競合を避けるために、このデフォルトの識別動作をオーバーライドする必要があります。
たとえば、以下の RuleUnit
クラスには、あらゆる種類のオブジェクトを許可する DataSource
定義が含まれています。
Unit0
ルールユニットクラスの例
public class Unit0 implements RuleUnit { private DataSource<Object> input; public DataSource<Object> getInput() { return input; } }
このルールユニットには、2 つの条件 (OOPath 表記) に基づいて別のルールユニットを保護する、以下の DRL ルールが含まれています。
ルールユニットの DRL ルール GuardAgeCheck
の例
package org.mypackage.myunit unit Unit0 rule GuardAgeCheck when $i: /input#Integer $s: /input#String then drools.guard( new AgeCheckUnit($i) ); drools.guard( new AgeCheckUnit($s.length()) ); end
保護された AgeCheckUnit
ルールユニットは、一連の persons
の年齢を検証します。AgeCheckUnit
には、確認用の persons
の DataSource
の定義、検証用の minAge
変数、および結果を集計する List
が含まれます。
AgeCheckUnit
ルールユニットの例
public class AgeCheckUnit implements RuleUnit { private final int minAge; private DataSource<Person> persons; private List<String> results; public AgeCheckUnit( int minAge ) { this.minAge = minAge; } public DataSource<Person> getPersons() { return persons; } public int getMinAge() { return minAge; } public List<String> getResults() { return results; } }
AgeCheckUnit
ルールユニットには、データソースの persons
の検証を実行する以下の DRL ルールが含まれます。
ルールユニットの DRL ルール CheckAge
の例
package org.mypackage.myunit unit AgeCheckUnit rule CheckAge when $p : /persons{ age > minAge } then results.add($p.getName() + ">" + minAge); end
この例では、RuleUnitExecutor
クラスを作成し、これらの 2 つのルールユニットが含まれる KIE ベースにクラスをバインドして、同じルールユニットの DataSource
定義を 2 つ作成します。
executor 定義とデータソース定義の例
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase ); DataSource<Object> input = executor.newDataSource( "input" ); DataSource<Person> persons = executor.newDataSource( "persons", new Person( "John", 42 ), new Person( "Sally", 4 ) ); List<String> results = new ArrayList<>(); executor.bindVariable( "results", results );
一部のオブジェクトを入力データソースに挿入し、Unit0
ルールユニットを実行できるようになりました。
挿入されたオブジェクトを使用したルールユニット実行の例
ds.insert("test"); ds.insert(3); ds.insert(4); executor.run(Unit0.class);
実行結果一覧の例
[Sally>3, John>3]
この例では、AgeCheckUnit
という名前のルールユニットはシングルトンクラスと見なされ、1 回のみ実行されます。この時、minAge
変数は 3
に設定されます。入力データソースに挿入された文字列 "test"
および整数 4
の両方は、minAge
変数が 4
に設定された 2 回目の実行をトリガーする可能性もあります。しかし、同じアイデンティティーを持つ別のルールユニットがすでに評価されているため、2 回目の実行はありません。
このルールユニットのアイデンティティーの競合を解決するには、AgeCheckUnit
クラスの getUnitIdentity()
メソッドをオーバーライドして、ルールユニットアイデンティティーに minAge
変数も含めます。
getUnitIdentity()
メソッドをオーバーライドする変更された AgeCheckUnit
ルールユニット
public class AgeCheckUnit implements RuleUnit { ... @Override public Identity getUnitIdentity() { return new Identity(getClass(), minAge); } }
このオーバーライドにより、以前のルールユニットの実行例は、以下の出力を生成します。
変更したルールユニットの実行結果一覧の例
[John>4, Sally>3, John>3]
minAge
が 3
と 4
に設定されたルールユニットは、2 つの異なるルールユニットとみなされるようになり、両方とも実行されます。