第2章 クイックスタート

2.1. クイックスタートの基本

JBoss Rules は、多くのユースケースに対応するようソフトウェアが設計されているため、非常に多くの機能があります。そのため、新規ユーザーは JBoss Rules に圧倒されてしまうことがあります。本章の目的はこれらの機能を少しずつ説明することです。理解しやすくするため、非常に簡単な例をいくつか使用します。

2.1.1. ステートレスナレッジセッション

最も簡単なユースケースは ステートレスセッション です。
ステートレスセッション
推論のないセッション。
ステートレスセッションでは、データを渡した後にその結果を受け取るため、関数のようであると言えます。ステートレスセッションの一般的なユースケースは多くありますが、その一部は次の通りです。
  • 検証。例:「この申請者は住宅ローンの資格があるか」
  • 計算。例: 「住宅ローンの保険料を計算する」
  • ルーティングとフィルタリング。例:「受信メッセージをフォルダーへフィルターする」または「受信メッセージを宛先へ送信する」
運転免許証の申請に関する簡単な例は次の通りです。
  1. 必要なデータを収集します。これはルールに渡される ファクト のセットを形成します。この例では、1 つのデータのみが存在します。
    package com.company.license;
    
    public class Applicant 
    {
        private String name;
        private int age;
        private boolean valid;
    
    	public Applicant (String name, int age, boolean valid)
    	{
    		this.name = name;
    		this.age = age;
    		this.valid = valid;
    	}
    
    	//add getters & setters here
    	
    }
  2. この時点でデータモデルが存在するため、最初のルールを記述します。このルールの目的は、18 歳未満の申請者を拒否することです。
    package com.company.license;
    
    rule "Is of valid age" 
    when 
        $a : Applicant( age < 18 ) 
    then 
        $a.setValid( false ); 
    end
    Applicant オブジェクトがルールエンジンに挿入されると、各ルールの制約が評価し、一致するものを探します (常に「オブジェクトタイプ」の暗黙的な制約があり、それ以降は明示的なフィールド制約がいくつでも存在できます)。
    パターン
    制約のコレクションはパターンと呼ばれます。
    パターンマッチング
    各ルールの制約グループがオブジェクトを評価し、一致するものを探すプロセスです。
    一致
    挿入されたオブジェクトがルールのすべての制約を満たす場合、一致したと見なされます。
    たとえば、Is of valid age ルールでは次の 2 つの制約があります。
    1. 一致するファクトのタイプは Applicant でなければならない。
    2. Age の値は 18 未満でなければならない。
    $a はバインディング変数で、ルールの結果 (ここからオブジェクトのプロパティーを更新可能) の一致したオブジェクトの参照を可能にします。

    注記

    ドル記号 ($) の使用は任意です。使用すると変数名とフィールド名を区別できます。

    注記

    ここでは、ルールがクラスと同じフォルダーにあり、最初の ナレッジベース を構築するために クラスパスリソースローダー を使用できると仮定します。
    ナレッジベース
    ナレッジベースは KnowledgeBuilder によってコンパイルされたルールのコレクションです。
  3. KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    kbuilder.add( ResourceFactory.newClassPathResource( 
    	"licenseApplication.drl", getClass() ), ResourceType.DRL );
    if ( kbuilder.hasErrors() ) {
        System.err.println( kbuilder.getErrors().toString() );
    }
    上記で引用符で囲まれたコードは newClassPathResource() メソッドを使用して licenseApplication.drl ファイルのクラスパスを検索します。ResourceTypeDrools ルール言語 で記述されます。
    Drools ルール言語
    Drools ルール言語 (DRL) は JBoss Rules のネイティブルール言語です。
  4. エラーは KnowledgeBuilder をチェックします。エラーがない場合、セッションを構築できます。
  5. ルールに対してデータを実行します (申請者は 18 歳未満であるため、申請は「無効」とマーク付けされます。
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
    kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); 
    StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
    Applicant applicant = new Applicant( "Mr John Smith", 16, true );
    
    assertTrue( applicant.isValid() );
    ksession.execute( applicant );
    assertFalse( applicant.isValid() );
ここまで、データは単一のオブジェクトで構成されていました。複数のオブジェクトを使用したい場合はどうするのでしょうか。この場合、collection など、オブジェクトを実装する iterable に対して実行することが可能です。次の例では、運転免許申請日が含まれる Application という名前の別のクラスを追加する方法を説明します。この他に、valid というブール値フィールドを Application に移動する方法についても説明します。
  1. コードは次の通りです。
    public class Applicant {
        private String name;
        private int age;
        
        public Applicant (String name, int age)
    	{
    		this.name = name;
    		this.age = age;
    	}
        // getter and setter methods here
    }
    
    public class Application {
        private Date dateApplied;
        private boolean valid;
    
        public Application (boolean valid)
        {
            this.valid = valid;
        }
        // getter and setter methods here
    
    }
  2. 申請が正規の期間内に行われたことを確認するために、以下のルールを追加します。
    package com.company.license
    
    rule "Is of valid age"
    when
        Applicant( age < 18 )
        $a : Application()     
    then
        $a.setValid( false );
    end
    
    rule "Application was made this year"
    when
        $a : Application( dateApplied > "01-jan-2009" )     
    then
        $a.setValid( false );
    end
  3. Java アレイは iterable インターフェースを実装できないため、JDK コンバーターメソッド を代わりに使用します (このメソッドは Arrays.asList(...) という行で始まります)。
    以下のコードは iterable リストに対して実行されます。各コレクション要素は、一致したルールが実行される前に挿入されます。
    StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
    kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
    Applicant applicant = new Applicant( "Mr John Smith", 16 );
    Application application = new Application(true);
    assertTrue( application.isValid() );
    ksession.execute( Arrays.asList( new Object[] {application, applicant} ));
    assertFalse( application.isValid() );

    注記

    execute(Object object) および execute(Iterable objects) メソッドは、実際は BatchExecutor インターフェースからの execute(Command command) というメソッドに対するラッパーです。
  4. CommandFactory を使用して命令を作成し、以下の内容が execute( Iterable it ) と同等になるようにします。
    ksession.execute( 
       CommandFactory.newInsertElements(Arrays.asList(new Object[] {application,applicant})) 
    );
  5. 多くのコマンドや結果出力識別子を使用する場合は、特に BatchExecutorCommandFactory が便利です。
    List<Command> cmds = new ArrayList<Command>();
    cmds.add( 
       CommandFactory.newInsertObject(new Person("Mr John Smith"), "mrSmith"));
    cmds.add( 
       CommandFactory.newInsertObject(new Person( "Mr John Doe" ), "mrDoe" ));
    
    ExecutionResults results = 
    	ksession.execute( CommandFactory.newBatchExecution(cmds) );
    
    assertEquals( new Person("Mr John Smith"), results.getValue("mrSmith") );

注記

CommandFactoryBatchExecutor で使用できる他のコマンドを多くサポートします。 StartProcessQuerySetGlobal などがその一例です。

2.1.2. ステートフルナレッジセッション

ステートフルセッション の一般的なユースケースの一部は次の通りです。
ステートフルセッション
ステートフルセッションでは、経時的にファクトに繰り返し変更を行うことが可能です。
  • 監視: 例として、株式市場を監視し、購入処理を自動化できます。
  • 診断: 例として、これを使用して障害検出処理を実行できます。また、医療診断処理にも使用できます。
  • 物流: 例として、配達物追跡および配送提供に関連する問題に適用できます。
  • 法令遵守: 例として、市場売買の合法性を検証するために使用できます。

警告

メモリーリークの発生を防ぐため、dispose() メソッドは、必ずステートフルセッションの実行後に呼び出されるようにしてください。これは、ナレッジベースは作成時にステートフルナレッジセッションへの参照を取得するためです。
StatelessKnowledgeSession の場合と同様に、StatefulKnowledgeSessionBatchExecutor インターフェースをサポートします。唯一の違いは、FireAllRules コマンドが最後に自動的に呼び出されないことです。
以下の火災報知器システムの開発に関する例は、「監視」のユースケースを表しています。
  1. 各部屋にスプリンクラーが設定されている家のモデルを作成します。いずれかの部屋で火事が発生します。
    public class Room 
    {
    	private String name
    		// getter and setter methods here
    }
    
    public class Sprinkler 
    {
    	private Room room;
    	private boolean on;
    	// getter and setter methods here
    }
    
    public class Fire 
    {
    	private Room room;
    	// getter and setter methods here
    }
    
    public class Alarm 
    {
    }
  2. ルールは複数のオブジェクト間の関係を表す必要があります (特定の部屋にスプリンクラーが存在することなどを定義するため)。
    これには、パターンでバインディング変数を制約として使用します。これは クロス積 になります。
  3. Fire クラスのインスタンスを作成します。このインスタンスをセッションに挿入します。
    以下のルールは、Fire オブジェクトの room フィールドへのバインディングを制約の一致へ追加します。これにより、その部屋のスプリンクラーのみがチェックされます。このルールが実行され、結果が実行されると、スプリンクラーが有効になります。
    rule "When there is a fire turn on the sprinkler"
    when
        Fire($room : room)
        $sprinkler : Sprinkler( room == $room, on == false )
    then
        modify( $sprinkler ) { setOn( true ) };
        System.out.println("Turn on the sprinkler for room "+$room.getName());
    end
    ステートレスセッションは標準的な Java 構文を使用してフィールドを変更しますが、上記のルールは modify ステートメントを使用します (「with」ステートメントのように動作します)。
    これには、一連のコンマ区切りの Java 式が含まれています。これらはあらゆる意味で、modify ステートメントの 制御式 によって選択されたオブジェクト "setters" への呼び出しです。これらの setters はデータを変更し、変更内容の「理由付け」を再度行えるよう、engine がその変更を認識するようにします。このプロセスが 推論 と呼ばれ、ステートフルセッションがどのように操作するかを理解するために重要になります (ステートレスセッションは推論を使用しないため、engine がデータの変更を認識する必要はありません)。

    注記

    推論を無効にするには、シーケンシャルモード を使用します。
ここまで、一致が存在する時に操作するルールを見てきましたが、一致が存在しない場合はどうするのでしょうか。火災が鎮火したことをどのように決定するのでしょうか。これまでの制約はエンジンが個別のインスタンスを制約する、命題論理 に基づいた「文」でした。しかし、JBoss Rules はデータのセットを閲覧できる 1 階論理 もサポートします。キーワードが not のパターンは何かが存在しない場合のみ一致します。
次のルールは火事が鎮火した時にスプリンクラーを停止します。
rule "When the fire is gone turn off the sprinkler"
when
    $room : Room( )
    $sprinkler : Sprinkler( room == $room, on == true )
    not Fire( room == $room )
then
    modify( $sprinkler ) { setOn( false ) };
    System.out.println("Turn off the sprinkler for room "+$room.getName());
end
各部屋に 1 つのスプリンクラーがありますが、建物全体には火災報知器が 1 つしかありません。火災がある時には Alarm オブジェクトが作成されますが、火元がいくつあっても建物全体で 1 つの Alarm のみが必要になります。ここで、not の補語である exists を導入できます。exists はカテゴリーの 1 つまたは複数のインスタンスに一致します。
rule "Raise the alarm when we have one or more fires"
when
    exists Fire()
then
    insert( new Alarm() );
    System.out.println( "Raise the alarm" );
end
火災が鎮火したら火災報知器を止める必要があります。火災報知器を停止するには再度 not を使用します。
rule "Cancel the alarm when all the fires have gone"
when
    not Fire()
    $alarm : Alarm()
then
    retract( $alarm );
    System.out.println( "Cancel the alarm" );
end
最後に、次のコードはアプリケーションが最初に起動した時と火災報知器とすべてのスプリンクラーが停止した時に、全体的な状態のメッセージを出力します。
rule "Status output when things are ok"
when
    not Alarm()
    not Sprinkler( on == true ) 
then
    System.out.println( "Everything is ok" );
end
ルールを fireAlarm.drl という名前のファイルに格納します。このファイルはクラスパスのサブディレクトリに保存します。次に、fireAlarm.drl という新しい名前で ナレッジベース を構築します。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newClassPathResource( "fireAlarm.drl", 
										getClass() ), ResourceType.DRL );
										
if ( kbuilder.hasErrors() )
	System.err.println( kbuilder.getErrors().toString() );

StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
次の例では、4 つの部屋オブジェクトが作成され、 1 つのスプリンクラーオブジェクトと共に各部屋に対し挿入されます (一致するプロセスは終了しますが、ルールはまだ実行されません)。
  1. ksession.fireAllRules() を呼び出します。これにより、一致するルールに実行権限が与えられますが、実行されないため健全性に関するメッセージのみが生成されます。
    String[] names = new String[]{"kitchen","bedroom","office","livingroom"};
    Map<String,Room> name2room = new HashMap<String,Room>();
    
    for( String name: names )
    {
    	Room room = new Room( name );
    	name2room.put( name, room );
    	ksession.insert( room );
    	Sprinkler sprinkler = new Sprinkler( room );
    	ksession.insert( sprinkler );
    }
    
    ksession.fireAllRules();
    > Everything is Okay
    
  2. 次に、2 つのファイルを作成し、挿入します ( ファクトハンドル は保持されます)。
    ファクトハンドル
    ファクトハンドルは挿入されたインスタンスのへの内部参照です。これにより、後でインスタンスを取り消したり変更したりすることができます。
  3. fires がエンジンにあるため、fireAllRules() を呼び出します。火災報知器が作動し、該当するスプリンクラーがオンになります。
    Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
    Fire officeFire = new Fire( name2room.get( "office" ) );
    
    FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
    FactHandle officeFireHandle = ksession.insert( officeFire );
    
    ksession.fireAllRules();
    > Raise the alarm
    > Turn on the sprinkler for room kitchen
    > Turn on the sprinkler for room office
    
  4. 鎮火したら、火災オブジェクトが取り消され、スプリンクラーがオフになります。この時点で火災報知器はキャンセルされ、状態メッセージが再度表示されます。
    ksession.retract( kitchenFireHandle );
    ksession.retract( officeFireHandle );
    
    ksession.fireAllRules();
    > Turn off the sprinkler for room office
    > Turn off the sprinkler for room kitchen
    > Cancel the alarm
    > Everything is ok
    
    > Turn off the sprinkler for room office
    > Turn off the sprinkler for room kitchen
    > Cancel the alarm
    > Everything is ok
    
これらの例は、ルールシステムの一部機能を実演する簡単な例であり、プログラミングの仕方が大まかに理解できたと思います。

2.2. 理論の概要

2.2.1. メソッドとルール

新しいユーザーはメソッドとルールを混同することがよくあります。メソッドの概要は次の通りです。
  • 直接呼び出される
  • 特定のインスタンスが渡される
  • 1 つの呼び出しによって 1 つの実行が行われる
public void helloWorld(Person person) 
{
	if ( person.getName().equals( "Chuck" ) ) 
	{
    	System.out.println( "Hello Chuck" );
	}
}
rule "Hello World"
when
    Person( name == "Chuck" )
then
    System.out.println( "Hello Chuck" );
end
ルールの概要は次の通りです。
  • engine に挿入されたデータに一致することにより実行される
  • 直接呼び出すことができない
  • 特定のインスタンスをルールに渡すことはできない
  • 一致によって、ルールは一度または複数回実行され、全く実行されないこともある

2.2.2. クロス積

クロス積
2 つ以上のデータセットが組み合わされた時、その結果はクロス積と呼ばれます。
火災報知器の例で、以下のルールについて考えてみましょう。
rule "show sprinklers in rooms"
when
    $room : Room()
    $sprinkler : Sprinkler()
then
    System.out.println( "room:" + $room.getName() +
        " sprinkler:" + $sprinkler.getRoom().getName() );
end
これは、select * from Room, Sprinkler への SQL (Structured Query Language) コマンドと同じで、Room テーブル内の各行が Sprinkler テーブル内の各行と結合するよう指示します。結果して、以下が出力されます。
room:office sprinker:office
room:office sprinkler:kitchen
room:office sprinkler:livingroom
room:office sprinkler:bedroom
room:kitchen sprinkler:office
room:kitchen sprinkler:kitchen
room:kitchen sprinkler:livingroom
room:kitchen sprinkler:bedroom
room:livingroom sprinkler:office
room:livingroom sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:livingroom sprinkler:bedroom
room:bedroom sprinkler:office
room:bedroom sprinkler:kitchen
room:bedroom sprinkler:livingroom
room:bedroom sprinkler:bedroom
クロス積は巨大になることがあり、パフォーマンス上問題となることがあります。このような問題を防ぐには、変数制約を使用して意味のない結果を除外します。
rule "show sprinklers in rooms"
when
    $room : Room()
    $sprinkler : Sprinkler( room == $room )
then
    System.out.println( "room:" + $room.getName() +
                        " sprinkler:" + $sprinkler.getRoom().getName() );
end
これにより、4 つの行のデータのみになり、正しい Sprinkler が各 Room に割り当てられます。SQL で書かれた通り、対応するクエリは select * from Room, Sprinkler where Room == Sprinkler.room になります。
room:office sprinkler:office
room:kitchen sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:bedroom sprinkler:bedroom

2.2.3. アクティベーション、アジェンダおよび競合セット

これまでの例は、データと一致プロセスが小さく、比較的単純でした。しかし、徐々に多くのファクトやルールを挿入することになります。現時点では、rule engine には結果の実行を管理する方法が必要となります。JBoss Rulesアクティベーションアジェンダ および 競合解決ストラテジー を使用してこれを実現します。
次の例はより複雑で、複数の期間にわたるキャッシュフロー計算の処理を実証します。

注記

ここでは、knowledge bases の作成に必要な Java コードの知識があることを前提とし、コードが繰り返されないように StatefulKnowledgeSession にファクトを投入します。
図は主要な段階での rule engine の状態を示します。
データモデルは CashflowAccount、および AccountPeriod の 3 つのクラスから構成されます。
public class Cashflow 
{
	private Date   date;
	private double amount;
	private int    type;
	long           accountNo;
	// getter and setter methods here
}

public class Account 
{
	private long   accountNo;
	private double balance;
	// getter and setter methods here
}

public AccountPeriod 
{
	private Date start;
	private Date end;
	// getter and setter methods here
}
knowledge bases の作成方法と、ファクトをインスタンス化して StatefulKnowledgeSession に投入する方法は理解されているはずです。したがって、説明を明確にするため、表を使用して挿入されたデータの状態を示します。下表は単一のファクトが Account に対して挿入されたことを表しています。2 四半期にわたる入出金も Cashflow オブジェクトとして Account に挿入されています。
図2.1「Cash Flow および Account」 は 4 つの Cashflow ファクトと共に挿入された単一の Account を表しています。
Cash Flow および Account

図2.1 Cash Flow および Account

以下の 2 つのルールは、指定された期間の入出金の合計を判断するために使用されます。さらに、口座残高を更新するために使用されます。(&& 演算子を使用するとフィールド名を繰り返し使用する必要がなくなります)。
rule "increase balance for credits"
when
  ap : AccountPeriod()
  acc : Account( $accountNo : accountNo )
  CashFlow( type == CREDIT,
    accountNo == $accountNo,
    date >= ap.start && <= ap.end,
    $amount : amount )
then
  acc.setBalance(acc.getBalance() + $amount); 
end
rule "decrease balance for debits" 
when 
    ap : AccountPeriod() 
    acc : Account( $accountNo : accountNo ) 
    CashFlow( type == DEBIT, 
        accountNo == $accountNo,
        date >= ap.start && <= ap.end, 
        $amount : amount ) 
then 
    acc.setBalance(acc.getBalance() - $amount);  
end
図2.2「Cash Flow および Account」 の通り、会計期間開始日は 1 月 1 日に設定され、終了日は 3 月 31 日に設定されます。これにより、データは入金と出金に対する 2 つの Cashflow オブジェクトに制約されます。
Cash Flow および Account

図2.2 Cash Flow および Account

  1. データは挿入段階で照合されますが、これはステートフルセッションであるため、ルールの結果は即座に実行されません。一致したルールと対応するデータは アクティベーション と呼ばれます。
  2. 各アクティベーションは アジェンダ と呼ばれるリストへ追加されます。
  3. アジェンダ上の各アクティベーションは、fireAllRules() メソッドが呼び出された時に実行されます。指定のない限り、アクティベーションは任意の順番で実行されます。
Cash Flow および Account

図2.3 Cash Flow および Account

上記すべてのアクティベーションが実行されると、口座の残高は -25 になります。
Cash Flow および Account

図2.4 Cash Flow および Account

会計期間が第 2 四半期に更新された場合、一致したデータ行は 1 つのみになるため、アジェンダ のアクティベーションも 1 つになります。
Cash Flow および Account

図2.5 Cash Flow および Account

アクティベーションが実行されると、残高は 25 になります。
Cash Flow および Account

図2.6 Cash Flow および Account

agenda に 1 つまたは複数のアクティベーションがある場合、アクティベーションは「競合」状態であると言えます。この場合、実行の順序を決定するため、競合解決ストラテジー が使用されます。最も単純なレベルでは、デフォルトのストラテジーは salience を使用してルールの優先度を決定します。各ルールのデフォルトの salience 値はゼロで、この値が大きいほど優先度が高くなります。salience に負の値を使用することも可能です。これにより、相対的にルールの実行順序を適用することができます。

注記

同じ salience 値を持つルールの実行順序は任意です。
これを示すため、次に口座残高を出力するルールを追加します。このルールはすべての入出金がすべての口座に適用された後に実行されます。salience が負の値であるため、デフォルトの salience 値 (ゼロ) を持つルールの後に実行されます。
rule "Print balance for AccountPeriod"
    salience -50
when
    ap : AccountPeriod()
    acc : Account()        
then
    System.out.println( acc.getAccountNo() + " : " + acc.getBalance() );
end
下表は結果となる agenda を表しています。3 つの入出金ルールが任意の順番で、出力ルールは最後であるため、入出金ルールの後に実行されます。

重要

JBoss Rules には ruleflow-group 属性が含まれています。ルールを実行できるタイミングを指定するために、この属性を使用してワークフロー図を宣言します。以下は、JBoss Developer Studio によって撮られたスクリーンショットで、2 つの ruleflow-group ノードがあります。これにより、レポートルールの前に計算ルールが実行されます。
Cash-Flow および Account

図2.7 Cash-Flow および Account

2.2.4. 推論

推論とは、ある一部のデータを使用して他のことを推論する行為のことです。たとえば、age フィールドと年齢ポリシー制御を提供するルールを持つ Person ファクトの場合、Person が成人または子供であるかを推論し、その推論に基づいたアクションを実行できます。

例2.1 成人の推論

rule "Infer Adult"
when
  $p : Person( age >= 18 )
then
  insert( new IsAdult( $p ) )
end
18 歳以上の Person には、IsAdult のインスタンスが挿入されます。このようなファクトは関係と呼ばれます。この推論された関係はすべてのルールで使用できます。
$p : Person()
IsAdult( person == $p )

2.2.4.1. 使用する推論

次の例では、ある政府部門が成人に ID カードを発行します。ID 部門は、ロンドンに居住している人が 18 歳以上の場合にカードを発行することを示す論理が含まれる決定テーブルを使用します。
モノリシック決定テーブル

図2.8 モノリシック決定テーブル

ID 部門は、誰が成人であるかについてポリシーを設定しません。中央政府が成人年齢を 21 歳に変更した場合は、変更管理プロセスが存在します。これを ID 部門に伝え、変更を反映してシステムが確実に更新されるようにする必要があります。
変更管理プロセスは高価で、エラーを引き起こすことがあります。ID 部門は、成人年齢を保存することにより 図2.8「モノリシック決定テーブル」 の決定テーブルに必要以上の情報を保持するため、この情報を最新の状態に保つ必要があります。
各部門が独自のルールを保持することにより、作成責任を分割または分離することが可能です。つまり、中央政府が成人年齢を変更した場合、他の機関 (ID 部門) が使用する新しいルールを用いて中央政府は中央リポジトリを更新します。
ルールテーブルの年齢ポリシー

図2.9 ルールテーブルの年齢ポリシー

これまで説明した IsAdult ファクトはポリシールールから推論されます。中央政府が IsAdult ファクトを保持するため、ID 部門は成人であるかどうかのみを知る必要があり、ルールが現在のポリシーに適合するよう維持する必要はありません。
ルールテーブルの ID カード

図2.10 ルールテーブルの ID カード

2.2.5. 推論および TruthMaintenance

真理維持 (Truth Maintence) および 論理的挿入 (logicalInsert) は、関心の分離を提供するために使用されます。以下の例は、子供または成人用のバス乗車パスを発行します。
rule "Issue Child Bus Pass" when
  $p : Person( age < 16 )
then
  insert(new ChildBusPass( $p ) );
end
 
rule "Issue Adult Bus Pass" when
  $p : Person( age >= 16 )
then
  insert(new AdultBusPass( $p ) );
end
関心の分離は logicalInsert を使用して実現できます。
rule "Infer Child" when
  $p : Person( age < 16 )
then
    logicalInsert( new IsChild( $p ) )
end
rule "Infer Adult" when
  $p : Person( age >= 16 )
then
    logicalInsert( new IsAdult( $p ) )
end
ファクトは、when 節の真偽に基づいて論理的に挿入されます。when 節の真偽が false に変更された場合、ファクトは自動的に取り消しされます。これは、相互排他的なルールには適切です。たとえば、年齢が 15 から 16 に変わった場合、IsChild ファクトは自動的に取り消され、IsAdult ファクトが挿入されます。
ここで、コードに戻り、乗車パスを発行できます。取り消しセットのカスケードに対する論理的挿入の連鎖を TMS がサポートするため、論理的に挿入することもできます。
rule "Issue Child Bus Pass" when
  $p : Person( )
       IsChild( person =$p )
then
  logicalInsert(new ChildBusPass( $p ) );
end
 
rule "Issue Adult Bus Pass" when
  $p : Person( age >= 16 )
       IsAdult( person =$p )
then
  logicalInsert(new AdultBusPass( $p ) );
end
LogicalInsertnot 条件要素と組み合わせて通知を処理できます。この場合、バス乗車パスの返却に対する要求を送信することができます。ChildBusPass オブジェクトが取り消されると、ルールがトリガーされ、要求が対象者に送信されます。
rule "Return ChildBusPass Request "when
  $p : Person( )
       not( ChildBusPass( person == $p ) )
then
    requestChildBusPass( $p );
end

2.3. 構築およびデプロイに関するその他のコメント

2.3.1. Change-Sets を使用したルールの追加

これまでの例では、JBoss Rules API を使用して knowledge bases を構築してきました。各ルールは手作業で追加しました。 JBoss Rules では、リソースを knowledge bases に追加するよう XML ファイルより宣言することもできます。この機能は change-set と呼ばれます。
change-set の XML ファイルには、knowledge base に追加できるルールリソースのリストが含まれています。このファイルを他のファイルに示すことも可能です。

重要

現時点では、change-sets は <add> 要素のみをサポートしています。Red Hat は将来的に <remove> および <modify> 要素のサポートを追加する予定です。
<change-set xmlns='http://drools.org/drools-5.0/change-set'
    xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
    xs:schemaLocation='http://drools.org/drools-5.0/change-set 
    drools-change-set-5.0.xsd' >
    
    <add>
        <resource source='http://hostname/myrules.drl' type='DRL' />
    </add>

</change-set>
URL は各リソースの場所を指定します。java.net.URL が提供するすべてのプロトコルがサポートされます。classpath と呼ばれるプロトコルの使用も可能です。このプロトコルは、リソースの current processes クラスパスを参照します。

重要

リソースに対して常に type 属性を指定する必要がありますが、ファイル名の拡張子からは推論されません。

注記

上記の XML を使用する場合、ResourceTypeCHANGE_SET に変更されていること以外は、前述のコードとほぼ同じであることに注意してください。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newClasspathResource( "myChangeSet.xml", 
	getClass() ), ResourceType.CHANGE_SET );

if ( kbuilder.hasErrors() ) {
	System.err.println( kbuilder.getErrors().toString() );
}
Change-sets には任意数のリソースを含めることが可能です。決定テーブルの設定情報を追加することもできます。次の例では、クラスパスプロトコルを介して、HTTP URL と決定テーブルのスプレッドシートの両方からルールをロードします。
<change-set xmlns='http://drools.org/drools-5.0/change-set' 
    xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
    xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
  <add>
    <resource source='http://hostname/myrules.drl' type='DRL' />
    <resource source='classpath:data/IntegrationTest.xls' type="DTABLE">
        <decisiontable-conf input-type="XLS" worksheet-name="Tables_2" />
    </resource>
  </add>
</change-set>
ディレクトリにあるすべてのファイルを追加するには、ディレクトリの名前をリソースソースとして使用します (すべてのファイルが指定された型である必要があります)。
<change-set xmlns='http://drools.org/drools-5.0/change-set'  
    xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'  
    xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
    <add>
        <resource source='file://rules/' type='DRL' />
    </add>
</change-set>

2.3.2. ナレッジエージェント

KnowledgeAgent は自動的にルールリソースをロード、再ロード、およびキャッシュします。 properties ファイルより設定します。
knowledge base によって使用されるリソースに変更があった場合、KnowledgeAgentknowledge base を更新または再構築できます。これらの更新にストラテジーを設定するには、KnowledgeAgentFactory を再設定します。
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent("MyAgent");
kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) );
KnowledgeBase kbase = kagent.getKnowledgeBase();
KnowledgeAgent は、追加された各リソースをスキャンします。デフォルトのポーリング間隔は60 秒です。リソースの「最終変更」日が変更された場合、 KnowledgeAgentknowledge base を再構築します (ディレクトリがリソースの 1 つとして設定された場合は、そのディレクトリのすべての内容が変更に対してスキャンされます)。

重要

以前の knowledge base の参照は変更後も存在するため、新たに構築された knowledge base へアクセスするには getKnowledgeBase() を呼び出す必要があります。
本章ではメソッドとルールの違いについて学習し、アジェンダ、アクティベーションおよび競合のセットがどのように動作し、KnowledgeAgentknowledge bases がどのように対話するか説明しました。これにより、本ソフトウェアの仕組みをより深く理解していただけたと思います。また、ステートレスセッションとステートフルセッションについてより包括的な知識を得られたことと思います。