Kogitoでルールエンジン開発が更に手軽になった


Kogitoについて

Kogitoというオープンソースコミュニティ、知ってますか?
Kogito、名前の由来は、ラテン語の"Cogito, ergo sum (我思う、故に我あり)" デカルトの有名な言葉から来ているとのことで、
読み方は、ˈkoː.d͡ʒi.to (KO-jee-to) 、カタカナで表すと、"コジト"。

Cogitoじゃなくて、Kogitoなのは、なぜかというと、

"文字 K は、KogitoのターゲットクラウドプラットフォームとしてのOpenShiftのベースであるKubernetesと、Kogitoの起源であるKnowledge Is Everything(KIE)オープンソースビジネス自動化プロジェクトを指します。"

KubernetesとKIEの"K"から来ているとのこと。
Kubernetesは、皆さんご存知ですね。
KIEって何?
KIEとは、こちらのオープンソースプロジェクトのことです。→ https://www.kiegroup.org/
Drools,jBPM,OptaPlanner,そして、ここにKogitoが新たに加わっています。

Drools,jBPM,OptaPlaannerを商用製品化したのが、Red Hat Decision Manager(とRed Hat Process Automation Manager)です。
そして、Red Hat Decision Managerのv7.11が先日(2021/6)リリースされ、Kogitoの一部機能が、製品として正式にサポート対象になりました!!

そこで、今日は、Kogitoを使うと、ルール開発がどのように変わるかについて、簡単に紹介します。
ルールの記述方法や、ルールエンジンの動き自体は、今までのDroolsと変わりませんが、
今まで以上に、業務ルールのマイクロサービス開発が手軽になります。

注)この記事内では、OSS版のKogitoを使ったサンプルを紹介していますが、Red Hat社の正式なサポートを受けるためには、OSS版ではなく、製品版をダウンロードする必要があります。

Kogitoサンプルを動かしてみる

Kogitoのサンプルプロジェクトが、GitHubにたくさんあります。

この中から、Excelのデシジョンテーブルを使ったQuarkusで動くアプリのサンプル"decisiontable-quarkus-example"を動かしてみます。
このサンプルは、ローン申し込み情報を元に、年齢や申込金額などから、OK/NGを判定して返却するルールになっています。

Git cloneするか、Zipダウンロードして、サンプルをローカルに展開します。

Kogitoの動作環境

Kogitoフレームワークは、QuarkusかSpringBootで動きます。
SpringBootに慣れている方であれば、そちらを選択してもOKですが、個人的にはQuarkusがおすすめです。
この記事では、Quarkus上でKogito使うサンプルを使います。

サンプルの中身確認

前提条件

READMEに、このサンプルの動作環境の前提条件が書かれています。

  • Java 11+ installed
  • Environment variable JAVA_HOME set accordingly
  • Maven 3.6.2+ installed

ネイティブコンパイルして動かしたい場合は、GraalVMが必要になります。
通常のJVM上で動かす場合は、OpenJDKなどでよいです。

サンプルの中身を見てみると、Javaクラスが3つと、Excelファイルが1つ確認できます。

pom.xml

まずは、pomの確認から。依存ライブラリを見ると、なんとなく何かがつかめる気がしますよね。

  <dependencies>
    <dependency>
      <groupId>org.kie.kogito</groupId>
      <artifactId>kogito-quarkus-rules</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    ...

kogitoとquarkusのプロジェクトが依存ライブラリとして指定されています。

まずは動かしてみる

とりあえず、動かしてみます。
Mavenでビルドして、

$ mvn clean package -Denforcer.skip=true -DskipTests=true

(enforcerをskipしないとERRORになったので、付けてる。Testは省略してる。)

そして、起動してみます。

$ java -jar target/quarkus-app/quarkus-run.jar

起動しました。ネイティブコンパイルはしていませんが、Quarkusはそれでも軽いですね。すばやく起動。

ルールのRESTサービスを動かしてみます。
curlでたたくか、何らかのRestClientを使うか、など手段はなんでもよいですが、
サンプルではSwaggerUIが入っているので、それを使って実行してみます。

http://localhost:8080/q/swagger-ui/ でSwaggerUIが開くはず。

4つのPOSTメソッドが使えるようです。
サンプルのREADMEに、/find-approved のテスト用データがありますので、それを貼り付けて実行してみます。

テストデータのJSONでは、JohnとPaulとGeorge、(ww)3人分の名前と年齢、ID、金額とデポジット額などが、loanApplicationにセットされ、渡されている様子ですね。
maxAmountとして5000がセットされています。

SwaggerUIで、/find-approvedを実行してみると、
実行結果は、コード200で成功、結果のJSONが確認できます。
3人の申し込み者のうち、Johnのデータのみが返却されており、approved=true となっているようです。John以外は、ローンが通らなかったようですね。

さてさて、では、次にルールの中身を実装を見てみましょう。
まず、ファクト定義を確認します。

ファクト定義

ルールエンジンで扱うデータは、Javaのオブジェクトとして定義します。
3つのJavaクラスを開いて確認します。

/decisiontable-quarkus-example/src/main/java/org/kie/kogito/queries/LoanUnit.java

public class LoanUnit implements RuleUnitData {

    private int maxAmount;

    private DataStore<LoanApplication> loanApplications;
    ・・・

このクラスは何か、というと、RuleUnitというものになります。
RuleUnitというのは、ルールと関連するファクト群とをひとまとめにすることができるものです。
RuleUnitを使わない実装も可能ですが、これを使うことで、Kogitoでは多くの実装コードを省略することができるので、お薦めです。
RuleUnitは、org.kie.kogito.rules.RuleUnitDataというInterfaceの実装クラスとして作成します。
ルール側に、どのルールユニットを使うかを定義すると、ルールユニット内の変数をそのままルール内で参照することができます。
このLoanUnitでは、maxAmountという、intと、LoanApplicationというオブジェクトが定義されています。

LoanApplicationクラスを見てみると、
/decisiontable-quarkus-example/src/main/java/org/kie/kogito/queries/LoanApplication.java

public class LoanApplication {

    private String id;

    private Applicant applicant;

    private int amount;

    private int deposit;

    private boolean approved = false;
    ・・・

こちらは普通のPOJOですね。

さらに、この中のApplicantクラスを見てみると、
/decisiontable-quarkus-example/src/main/java/org/kie/kogito/queries/Applicant.java

public class Applicant {

    private String name;
    private int age;
    ・・・

ということで、サンプルに含まれる3つのJavaクラスは、2つのデータオブジェクトとルールユニットということがわかりました。

ルール定義

では、デシジョンテーブルを見てみます。
/decisiontable-quarkus-example/src/main/resources/org/kie/kogito/queries/LoanUnit.xls
ここにありますね。xlsファイルです。(xlsxでも大丈夫です)

RuleTable部分を見ると、agedepositamountの組み合わせによって、ローン承認OK/NGを判定するルール、のようですね。シンプルなデシジョンテーブルです。

そして、RuleSetの定義の下に、
Unit LoanUnit
とある通り、ルールユニットの定義がなされています。この宣言により、さきほどのLoanUnitをエントリーポイントとしてルール内で使うことが出来ます。

1行目のルールをDRLで書くと、こうなります。

when
   $1: /loanApplications [applicant.age >= 20, deposit < 1000, amount <= 2000 ]
then
    modify($l) { setApproved(true) };
end

これ、OOPathで書かれています。
/loanApplications は、LoanUnitルールユニットの変数、loanApplicationsを指しています。そして、条件節は、[ ]で記述します。
OOPathを使わない場合は、従来どおりの記述で、

when
   $1: LoanApplication(applicant.age >= 20, deposit < 1000, amount <= 2000) from loanApplications
・・・

と書いてもよいです。

3列目のCONDITION列で、maxAmountという変数がでていますが、これは、LoanUnitmaxAmountを指しています。ローンのMAX金額は、外から渡せるようにしてあるわけですね。

エンドポイントはどこで作っているのか?

さて、ルール定義とファクト定義は、なんとなく理解できたのではないかと思います。
が、いくつか気になるところがあるはずです。

私は一番最初にこのサンプルを動かした時に、2つの疑問がでました。

  1. ルールを実行した際に、/find-approvedエンドポイントを指定したが、そのエンドポイントはどこで作成しているのか

  2. /find-approved を実行して、JohnとPaulとGeorgeの3人分のデータを渡したら、Johnだけが返却された。でもデシジョンテーブル見る限りだと、trueかfalseかをセットしているだけに見えるので、Johnがtrue、PaulとGeorgeはfalseがセットされて返却されると想像したのに、なぜJohnだけが返却されたのか?(どこで絞り込みをかけてる?)

まあでも、デシジョンテーブル見ると、それらしい怪しい箇所が見つかるんですね。
ここです↓

Queryが2つ、書かれています。
クエリとは、ルールの条件節を、名前をつけて定義できるものです。
ちょっと読みづらいので、整形してみます。

query FindApproved
   $l: /loanApplications[ approved ]
end
query FindNotApprovedIdAndAmount
   $l: /loanApplications[ !approved, $id: id, $amount: amount ]
end

最初のquery FindApproved は、LoanApplicationオブジェクトで、approved = true、という条件が定義されています。
承認OKのJohnだけが抽出された理由は、どうやらこのクエリが実行されたから、のようですね。

でも、このクエリはどこで誰が呼び出しているのか?

というと、実は、これをKogitoが自動でやってくれています。
Kogitoは、query定義から、クエリ実行クラスと、RESTのエンドポイントを自動作成します。

query FindApproved と定義した場合は、 /find-approvedというエンドポイントを自動で生成します。そして、そのエンドポイントの実行時に、該当クエリが自動で実行されます。

サンプルにクエリを追加してみよう

ということで、それでは、自分で新たなクエリ定義を追加して、確認してみたいと思います。

承認OK/NGに関わらず、投げたLoanApplicationの結果を全取得するシンプルなクエリを定義してみましょう。(通常、これがまず最初に必要になる気がする)

query GetAll
   $l: /loanApplications
end

単にFindApprovedの絞り込み条件なし、バージョンです。
Excelのデシジョンテーブルに上記クエリを追記し(query定義のセル内に追記する)、プロジェクトを再ビルドして、起動し、SwaggerUI見てみると・・・

/get-all エンドポイントが追加されています!
ちなみに、〜/firstというのも自動追加されますが、これは結果ファクトのうち1件だけ取り出すようです。

同じJSONデータでこのRESTエンドポイントを実行してみます。
すると、今度は3人分のルール実行結果が、絞り込みなくそのまま返されました。

[
  {
    "id": "ABC10015",
    "applicant": {
      "name": "George",
      "age": 12
    },
    "amount": 1000,
    "deposit": 100,
    "approved": false
  },
  {
    "id": "ABC10001",
    "applicant": {
      "name": "John",
      "age": 45
    },
    "amount": 2000,
    "deposit": 100,
    "approved": true
  },
  {
    "id": "ABC10002",
    "applicant": {
      "name": "Paul",
      "age": 25
    },
    "amount": 5000,
    "deposit": 100,
    "approved": false
  }
]

エンドポイントの生成のされかたは、
FindApproved → /find-approved
FindNotApprovedIdAndAmount → /find-not-approved-id-and-amount
GetAll → /get-all
というようになるようです。大文字のところで切って、ハイフンつなぎになるようですね。

まとめ

Kogitoでは、ルールの記述方法や、ルールエンジンの動作自体は、従来と変わりません。
変わったところは、RuleUnitやQueryを使うことで、従来だとJavaコードでいくらかの実装が必要だった箇所が、自動生成されるようになったこと、Quarkus(またはSpringBoot)上で軽量なルールのサービスを素早く起動することが可能になったことです。(JakartaEEベースのAPサーバにKIEサーバをデプロイ、という重厚な備えが不要になった)

業務ルールのマイクロサービス開発が、ますます手軽に出来るようになりましたね。

まだ、従来の仕組みで動作しているルールを、すべてKogitoで動かせるわけではないですが、新しく作成するルールのサービスは、Kogitoを利用してみると良いかもしれません。

Kogitoについて、詳しくはこちらを参照ください。↓(OSS版のドキュメントです)