Droolsブログ : 04 ステートレス ksession

04 ステートレス ksession

ここまで、普通の ksession つまり ステートフル ksession を使ってきましたが、今回は ステートレス ksession の使い方です。

ステートレス ksession とは、「ステートフル ksession のラッパーで、insert, fireAllrules, dispose をまとめてやってくれる便利クラス」です。「まとめてやって、終わってくれる」ので、ユーザが ksession の状態(ステート)を気にする必要はありません。(StatelessKieSession オブジェクト自体に状態が無いわけではないので、名前がちょっとミスリーディングなんですけど。。。)

サンプルではステートフル ksession を使うことが多いのですが、実際のユースケースでは ステートレス ksession で十分な場合が多く、またそのほうがコードも簡潔になるのでおすすめです。

サンプルコードはこちらから clone してください。

git clone https://github.com/tkobayas/drools-blog.git

今日のエントリはその中の 04_stateless です。

ルールは前回の 03 推論(Inference) と全く同じです。Java コードの以下の部分だけが違っています。

        StatelessKieSession ksession = kcontainer.newStatelessKieSession();

newKieSession() のかわりに newStatelessKieSession() で ksession を取得します。

        Command insertElementsCommand = CommandFactory.newInsertElements(Arrays.asList(john, order));
        List<InternalFactHandle> factHandleList = (List<InternalFactHandle>) ksession.execute(insertElementsCommand);

Insert したいファクトを List にして newInsertElements() で Command を作成します。その Command を ksession.execute() に渡せばOKです。

Insert だけしか指示していないようですが、最後に自動的に fireAllRules() を行い、さらに dispose() もやってくれます。

mvn test の結果は 03 推論(Inference) のときと同じであることが確認できると思います。

ステートレス ksession は上記のような、InsertElementsCommand 以外にも様々な Command を実行できます。複数の Command をまとめて BatchExecutionCommand にして実行させることもできます。いずれにせよ ksession.execute() で全てが実行され、終了します。

Logic Pro X プラグイン

Hexagon Sky

www.samplescience.ca

Boards Of Canada 風シンセ。Logic のプリセットシンセは今ひとつ好みに合わないので(といってもES2もAlchemyもまるで使いこなせてないが)ありがたい。

注意点

  • AU (Universal じゃないやつ) を Macintosh HD/Library/Audio/Plug-Ins/Components に置く
  • Universal はピアノロールがバグった
  • AU でもプロジェクト間の貼り付けをやった後、プロジェクトが開けなくなった。こまめに名前変えて保存、WAVへのバウンスした方がいい

  • オートメーションもコントローラーのアサインも効かない。あー、あまり使えないかもー

Droolsブログ : 03 推論(Inference)

03 推論(Inference)

今回は Drools の重要な機能のひとつ、推論(Inference)を紹介します。推論というと少し難しく聞こえますが、「再評価」と言い換えてもよいです。

サンプルコードはこちらから clone してください。

git clone https://github.com/tkobayas/drools-blog.git

今日のエントリはその中の 03_inference です。

以下のルールを見てください。前回と同様のポイント計算ルールですが、前回より簡略化しています(ポイント率の話は抜き)。

rule "春のキャンペーン"
    when
     $o : Order(consumer.memberCreatedAt >= "2019-04-01" && consumer.memberCreatedAt <=  "2019-04-30")
    then
        $o.setExtraPoint($o.getExtraPoint() + 2000);
        update($o);
end

rule "高額商品キャンペーン"
    when
     $o : Order(itemPrice > 100000)
    then
        $o.setExtraPoint($o.getExtraPoint() + 4000);
        update($o);
end

rule "大量ポイント獲得オーダー"
    when
     $o : Order(extraPoint > 5000)
    then
        $o.setSpecialPointOrder(true);
end

"update($o)" というのが今回の肝です。キャンペーンルールに該当し、ポイントが追加された場合、「update()」によりルールの再評価を行います。このとき「$o」つまり Order が更新されたよ、ということを伝えています。

最後のルール "大量ポイント獲得オーダー" は、"春のキャンペーン" と "高額商品キャンペーン" の両方に該当してポイントが 6000 になってはじめてマッチします。このようにルールの実行中に条件が変わってマッチするようなケースに「再評価」が必要になります。ファクトの更新「update()」以外にも挿入「insert()」や削除「delete()」も再評価のトリガーになります。

ここで「あれ? "春のキャンペーン" や "高額商品キャンペーン" も再評価されて2重に実行されたりしないの?」と疑問に思った人もいるかも知れません。実は重要なポイントです。さっき「$o が更新されたよ、と伝える」と書きましたがこのときルールエンジンは $o のどのプロパティが変更されたかを意識します。つまり $o の extraPoint が変更された、と知っているので consumer や itemPrice は再評価しないのです。これは「Property Reactive」という機能で、Drools のバージョンによってデフォルトの動作が違う場合があります。このサンプルで使用している Drools 7.18.0.Final ではデフォルトで「Property Reactive」が有効です。

実行してみましょう。

$ cd 03_inference
$ mvn clean test

以下のようなアウトプットが出ます。

insert : Person [name=ジョン, memberCreatedAt=2019-04-11]
insert : Order [consumer=ジョン, itemName=ギター, itemPrice=200000]
実行 : 春のキャンペーン
実行 : 高額商品キャンペーン
実行 : 大量ポイント獲得オーダー
======================================
ポイントキャンペーンのご活用ありがとうございます!
======================================

ここで、"大量ポイント獲得オーダー" による specialPointOrder フラグはメッセージの表示にしか使っていませんが、アプリでの特別なオーダー処理や、更なるルールの判定につながるような使い方も考えられられます。

推論は強力な機能ですがユースケースによっては使う必要がないでしょう。使うときも自分が混乱しない程度に抑えて使うことをおすすめします。

Droolsブログ : 02 デシジョンテーブル

02 デシジョンテーブル

前回は HelloWorld ということで基本の DRL でルールを書きましたが、次はよく使われる「デシジョンテーブル」を説明します。

デシジョンテーブル(意思決定表)は以下のように表形式で、条件と結果を表すものです。DRL よりずっと分かりやすいですね。

f:id:tokobayashi:20190501110330p:plain

MS ExcelLibreOffice で書く XLS/XLSX 形式と、Drools のワークベンチ GUI (Decision Central)で書く GDST 形式がありますが、ここでは前者を説明します。

デシジョンテーブルのメリットは、Drools に詳しいメンバーが下準備さえ済ませれば、あとはビジネスユーザーがセルの値を埋めて行くだけ、という分業が簡単に行えることです。もちろん表にするまでもないルールは直接 DRL を書いて組み合わせることもあります。

実際のところ、デシジョンテーブルは DRL 生成テンプレートです。1行あたり、1ルールが内部的に生成されます。例えば上記の「ノーマルカード」の行は以下のような DRL になります。

rule "PointCalc_11"
    when
        $o : Order(consumer.card == MembershipCard.NORMAL)
    then
        $o.setPointRate($o.getPointRate().add(new BigDecimal("1.0")));
end
...

イメージついたでしょうか?

サンプルコードはこちらから clone してください。

git clone https://github.com/tkobayas/drools-blog.git

今日のエントリはその中の 02_decisiontable です。お題は買い物した時のポイント付与サービスです。今回は4つのルールだけですが、実際にはものすごい量のルールだったりしますよね。

まずプロジェクト内のデシジョンテーブル

https://github.com/tkobayas/drools-blog/blob/master/02_decisiontable/src/main/resources/org/example/point-calc.xls

を見てみましょう。上記画像と同じものです。中身を順番に説明していきます。

まず前半は表共通の設定事項です。

  • 2行目: RuleSet の次のセルはパッケージ名です
  • 3行目: Import の次のセルは import するクラス名をカンマ区切りで並べます
  • 4行目: Notes の次のセルはただの説明です。ルールには反映されません
  • 6行目: RuleTable は半角スペース空けて、同じセルに表の名前を書きます。この名前が生成される各ルールのプリフィックスになります
  • 7行目: 条件部は「CONDITION」、結果部は「ACTION」と記入します。DRLにおける 「when」「then」に相当します。必要に応じて何列でも追加できます。
  • 8行目: 「CONDITION」の場合のみ、条件ファクトを指定します
  • 9行目: 「CONDITION」の場合は、上記ファクトの制約条件を記入します。テンプレートの挿入変数がひとつの場合は「$param」、複数の場合は「$1」「$2」...と書きます。「ACTION」の場合は「then」で実行したい Java コードを書きます。同様にテンプレートの挿入変数が使えます。

後半が実際にテンプレートに挿入する値となります。

  • 10行目: コメント用。以下のセルに何を入れればよいのかわかりやすく書きましょう。
  • 11行目: ここ以降が1行あたり1ルールに相当します。セルの値が上記9行目のテンプレートに適用されます。複数変数の場合はカンマ区切りです。空欄の場合はその列は丸ごと使用されません。B列も単なるコメントです。
  • 12行目: 以下同様

次に Java コードを見てみましょう。

https://github.com/tkobayas/drools-blog/blob/master/02_decisiontable/src/test/java/org/example/DroolsTest.java

        // デフォルトの dateformat は "dd-MMM-yyyy" (例: "01-Apr-2019") なので変更する
       System.setProperty("drools.dateformat", "yyyy-MM-dd");

まず日付フォーマットは日本向け ("yyyy-MM-dd") に変更しておきます。ソースコメントのとおりです。

        SpreadsheetCompiler compiler = new SpreadsheetCompiler();
        String drl = compiler.compile(ks.getResources().newClassPathResource("org/example/point-calc.xls").getInputStream(), InputType.XLS);
        System.out.println(drl);

これはデバッグ用コードで、本当は省いても構いません。このコードにより、デシジョンテーブルから変換された DRL を見る事ができます。ルールのコンパイルエラーなどがあったときには重宝します。

        KieContainer kcontainer = ks.getKieClasspathContainer();
        KieSession ksession = kcontainer.newKieSession();
        ...
        ksession.insert(john);
        ...
        ksession.insert(order);
        ...
        int fired = ksession.fireAllRules();

あとは HelloWorld と同じですね。

さて、実際に動かしてみましょう。

ジョンが20万円のギターを買いました。メンバーズカードはシルバーなので 2% 還元。また、この4月に入会したので「春の特別キャンペーン」が適用され、還元率 0.5 アップの 2.5%。更に特別ポイントが 1000 ポイント加算されます。

$ cd 02_decisiontable
$ mvn clean test

...

+++ ルール実行開始 +++
insert : Person [name=ジョン, memberCreatedAt=2019-04-11, card=SILVER]
insert : Order [consumer=ジョン, itemName=ギター, itemPrice=200000]
======================================
お買い上げにより、 6000 ポイントが付与されます

200000*0.025+1000=6000ですね。

Droolsブログ : 01 Drools HelloWorld

01 Drools HelloWorld

こんにちは、これから Drools についていろいろと書いていこうと思います。サンプルと共に、分かりやすい入門的なエントリを中心にしていく予定です。

Drools というのはオープンソースのルールエンジンで、Java で書かれています。

https://www.drools.org/

ルールエンジンとは、簡単に言うとインプットをルールに基づいて処理してアウトプットする、というエンジンです。あ、普通のプログラミングと同じですね?そうです、では普通の手続き型プログラミング言語(例えば Java)とどう違うかというと、ルールは宣言的(Declarative)に書かれる、ということです。例えばこのルールを見てください。

rule "Hello Child"
    when
        $p : Person( age < 12 )
    then
        // 何かする
end

rule "Hello Adult"
    when
        $p : Person( age >= 20 )
    then
        // 何かする
end

1つ目のルールは「12歳未満なら XXX する」

2つ目のルールは「20歳以上なら YYY する」

というものです。Java であれば

if (person.getAge() < 12) {
    // 何かする
}
if (person.getAge() >= 20) {
    // 何かする
}

のようになるでしょう。一見たいして変わらないようですが、大きな違いがあります。Java の場合は「書かれた順番通りに処理される」のです。数千、数万の複雑なルールの場合、全てを上から順番に処理するのは現実的ではないでしょう。プログラマは最適化のために、様々な手を打てます(データによって不要な部分を評価しない、共通部分をまとめて評価、キャッシュ/インデックス化など)。しかしそれをするとおそらくルールは最適化ロジックと混じり合い、ビジネスユーザが解読できないものになるでしょう。また、その後のルール追加、変更も困難になります。

ルールエンジンの場合、最適化はエンジンがやってくれます。ユーザは単純にルールを並列に並べるだけです。(もちろん「順番」がルール上必要な場合はいくつかの手段、文法があります。それはまた後日)

さて、概要はこのくらいにして早速 HelloWorld しましょう。

サンプルコードはこちらから clone してください。

git clone https://github.com/tkobayas/drools-blog.git

今日のエントリはその中の 01_helloworld です。

ではソースコードを見てみましょう。

まずルールです。

https://github.com/tkobayas/drools-blog/blob/master/01_helloworld/src/main/resources/org/example/Sample.drl

package org.example
 
import org.example.Person;

rule "Hello Child"
    when
        $p : Person( age < 12 )
    then
        System.out.println( "Hello Child, " + $p.getName());
end

rule "Hello Adult"
    when
        $p : Person( age >= 20 )
    then
        System.out.println( "Hello Adult, " + $p.getName());
end

これは DRL (Drools Rule Language) という言語で書かれています。新しい言語を覚えるというと若干ハードルが高いですが、結構簡単です。見た通り、"when"に条件を書き、"then"に結果を書くだけです。"then"のところは普通の Java で書けます。"$p" はバインド変数です。マッチした Person を "then" で参照できます。

ではこのルールをどう実行するかというと、こちらの Java コードを見てください。

https://github.com/tkobayas/drools-blog/blob/master/01_helloworld/src/test/java/org/example/DroolsTest.java

        KieServices ks = KieServices.Factory.get();
        KieContainer kcontainer = ks.getKieClasspathContainer();
        KieSession ksession = kcontainer.newKieSession();

この3行は定型処理と思ってください。クラスパスにルールを置いた場合はこうなりますが、別の方法も後日紹介します。KieSession が、ユーザが使うエンジンインターフェースです。ksession と略されることもあります。

        Person john = new Person("ジョン", 25);
        ksession.insert(john);
        Person paul = new Person("ポール", 10);
        ksession.insert(paul);

2つの Person オブジェクトを ksession に insert します。Person はただの POJO です。ファクト(Fact) と呼ばれることもあります。

        int fired = ksession.fireAllRules();
        assertEquals(2, fired);

fireAllRules() でマッチしたルールが実行(fire)されます。戻り値は実行されたルールの数です。

        ksession.dispose();

最後、dispose() は忘れないように呼んでください。ksession に紐づいたリソースを解放します。

では maven で実行してみましょう。

$ cd 01_helloworld
$ mvn clean test

アウトプットはこんな感じになるでしょう

...
Running org.example.DroolsTest
...
Hello Child, ポール
Hello Adult, ジョン
...

ルール通りに出力されましたね。

まずはここまで!