Red Hat Decision Managerのルール実行環境をSpringBootで素早く構築してルールアプリを動かしてみる(後編)


前編では、KieServer on Spring Bootを起動するところまでを書いています。
この記事はその続きで、ルールアプリをKieServer on Spring Bootにデプロイして実行するところについてです。

前編はこちら

まずは、Red Hat Decision Managerのルール実行環境をSpringBootで素早く構築してルールアプリを動かしてみる(前編)からどうぞ。

(後編) ルールアプリを作成しKieServer上で実行する

次に、ダウンロードしたzipを解凍して出来た、3つのプロジェクトは、ほぼ中身が空ですので、ここにルールアプリを追加して動かしてみましょう。

プロジェクト構成

3つのプロジェクトを確認します。お好きなIDE上に展開してください。(IDE無くても良いですが、まあ使ったほうが便利かと)

  • <business-application>-kjar ---ルール定義はこのプロジェクトに配置
  • <business-application>-model ---ファクトをここに配置(ファクトはkjarに含めてもよいが、ファクトはルール呼び出し側でも共有することが多いので、別プロジェクトにするのが一般的)
  • <business-application>-service ---Spring Bootのアプリはここ。今回はここはいじらない。

サンプルルールアプリの概要

簡単なルールアプリを作成します。

ルール概要

とある加工製品の注文データをもとに、注文の可否を判定して結果を返却する。

ファクト

注文者情報と注文情報、2つのファクトを渡す。

注文者ファクト 備考
顧客ID
顧客種別 普通/大口
注文上限 大口顧客の場合の注文数上限
注文ファクト 備考
注文ID
顧客ID
商品ID
注文数量
加工種別 カットなし/カット極小/カット中/カット大
注文可否 デフォルトはtrue(可)
コメント 否の場合の理由
ルール詳細

以下にあてはまる場合は、注文不可とする。それ以外は注文可とする。

  • 商品IDが"T"で始まる商品、かつ加工種別が"カット大"、かつ注文数量が30以下
  • 商品IDが"M"で始まる商品、かつ加工種別が"カット極小"、かつ注文数量が50以下
  • 顧客種別が"大口"以外、かつ注文数量が100以上
  • 顧客種別が"大口"かつ、注文数量>注文上限
ファクト実装

<business-application>-model/src/main/java/com/company/model配下に、注文者クラスと注文クラスを新規追加しましょう。

注文者.java
package com.company.model;

import lombok.Data;

@Data
public class 注文者 {

    private String 顧客ID;
    private String 顧客種別;
    private int 注文上限;
}
注文.java
package com.company.model;

import lombok.Data;

@Data
public class 注文 {

    private String 注文ID;
    private String 顧客ID;
    private String 商品ID;
    private int 注文数量;
    private String 加工種別;

    private boolean 注文可否 = true;
    private String コメント;

}

ここでは、lombok使っています。lombok使わない場合は、それぞれのフィールドにgetter,setterを明示的に実装してください。(ルールはgetter,setterメソッドを使ってファクトの価を参照するため、必須です)
VSCodeのLombok拡張機能、Lombok Annotations Support for VS Codeを入れると簡単にlombok使えます。(pom.xmlにdependency追記も忘れずに)

ルール実装

DRLでルール書きます。(Excelでももちろん書けますよ)
<business-application>-kjar/src/main/resources配下に、ルール格納用フォルダを作り、そこにDRLファイルを新規作成します。

ordercheckフォルダを新規作成し、その配下に注文可否判定.drlを新規作成しました。
先ほどの4つのルールを、DRLで実装すると、こんな感じになります。

注文可否判定.drl
package ordercheck;

import com.company.model.*;

rule "T商品の加工種別と数量基準内チェック"
    when
        $注文:注文(商品ID matches "T.*", 加工種別 == "カット大", 注文数量 <= 30)
    then
        modify($注文) {
            set注文可否(false),
            setコメント("注文数下限未満(T_大)")
        }
end

rule "M商品の加工種別と数量基準内チェック"
    when
        $注文:注文(商品ID matches "M.*", 加工種別 == "カット極小", 注文数量 <= 50)
    then
        modify($注文) {
            set注文可否(false),
            setコメント("注文数下限未満(M_極小)")
        }
end

rule "大口顧客以外の注文数量上限チェック"
    when
        注文者(顧客種別 != "大口", $ID:顧客ID)
        $注文:注文(顧客ID == $ID, 注文数量 >= 100)
    then
        modify($注文) {
            set注文可否(false),
            setコメント("注文数上限超過")
        }
end

rule "大口顧客の注文数量上限チェック"
    when
        注文者(顧客種別 == "大口", $ID:顧客ID, $注文上限:注文上限)
        $注文:注文(顧客ID == $ID, 注文数量 > $注文上限)
    then
        modify($注文) {
            set注文可否(false),
            setコメント("注文数上限超過(大口)")
        }
end

このルールモジュールの定義を<business-application>-kjar/src/main/resources/META-INF/kmodule.xmlに以下のように追加します。

kmodule.xml
<kmodule xmlns="http://www.drools.org/xsd/kmodule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <kbase name="ordercheck" packages="ordercheck">
        <ksession name="ksession-ordercheck" type="stateless"/>
    </kbase>
</kmodule>

そして、<business-applicatiton>-kjar/pom.xmlに、ファクトのjarを参照するよう、dependencyを追加します。

pom.xml
<dependencies>
      <dependency>
        <groupId>com.company</groupId>
        <artifactId>business-application-model</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>
  </dependencies>

さて、これでルールアプリが完成しました。
ビルドしてデプロイしてみましょう。

デプロイ

再度、KieServer on Spring Bootを起動します。
(以下コマンドで、起動する際に、再ビルドします。)

$ cd business-application-service/
$  ./launch.sh clean install
ルール実行

ルールアプリをデプロイしたので、KieServer上に、KieContainerが作成されているはずです。
curlで確認してみましょう。

curl -X GET --header 'Accept: application/json' 'http://localhost:8090/rest/server/conainers' -u user:user

KieContainerの情報が取得できたら、container-idを探してメモします。

{
  "type" : "SUCCESS",
  "msg" : "List of created containers",
  "result" : {
    "kie-containers" : {
      "kie-container" : [ {
        "container-id" : "business-application-kjar-1_0-SNAPSHOT",
        "release-id" : {
          "group-id" : "com.company",
          "artifact-id" : "business-application-kjar",
          "version" : "1.0-SNAPSHOT"
        },

中略
  }

"container-id" : "business-application-kjar-1_0-SNAPSHOT"とありますね。
ルール実行の際は、このコンテナIDを指定する必要があります。

ルール実行APIについては、以前のブログ(Red Hat Decision Manager - Decision Serverにルールをデプロイして呼び出す)に少し解説していますので、こちらを参照ください。

さて、まずは、いずれのルールにも一致しないデータでルールを実行してみましょう。
注文可否=trueで結果が返ってくるはずです。

  • 商品IDが"T"で始まる商品、かつ加工種別が"カット大"、かつ注文数量が30以下
  • 商品IDが"M"で始まる商品、かつ加工種別が"カット極小"、かつ注文数量が50以下
  • 顧客種別が"大口"以外、かつ注文数量が100以上
  • 顧客種別が"大口"かつ、注文数量>注文上限
curlコマンド
 curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ 
   "lookup": "ksession-ordercheck", 
   "commands":[ 
     {"insert":{"object":{"com.company.model.注文者":{"顧客ID":"1001","顧客種別":"普通"}}}},
     {"insert":{"object":{"com.company.model.注文":{"注文ID":"O-501","顧客ID":"1001","商品ID":"RA29001","加工種別":"カット大","注文数量":50}},"out-identifier":"注文結果"}}, 
     {"fire-all-rules":""} 
   ] 
 }' 'http://localhost:8090/rest/server/containers/instances/business-application-kjar-1_0-SNAPSHOT'

ルールに渡しているファクトの中身に注目してください。
顧客種別=普通なので、大口顧客ではなく、注文数量=50なので、ルールに一致しません。
また、商品IDはT始まりでもM始まりでもないので、ルールに一致しません。

結果
{
  "type" : "SUCCESS",
  "msg" : "Container business-application-kjar-1_0-SNAPSHOT successfully called.",
  "result" : {
    "execution-results" : {
      "results" : [ {
        "value" : 0,
        "key" : ""
      }, {
        "value" : {"com.company.model.注文":{
  "注文ID" : "O-501",
  "顧客ID" : "1001",
  "商品ID" : "RA29001",
  "注文数量" : 80,
  "加工種別" : "カット大",
  "注文可否" : true,
  "コメント" : null
}},
        "key" : "注文結果"
      } ],
      "facts" : [ {
        "value" : {"org.drools.core.common.DefaultFactHandle":{
  "external-form" : "0:2:2067392882:1330587205:2:DEFAULT:NON_TRAIT:com.company.model.注文"
}},
        "key" : "注文結果"
      } ]
    }
  }

結果のファクトを見ると、注文可否=trueになっていることが確認できますね。

では、ルールに渡すファクトの中身を一部変えて、再度実行してみましょう。

curlコマンド
 curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ 
   "lookup": "ksession-ordercheck", 
   "commands":[ 
     {"insert":{"object":{"com.company.model.注文者":{"顧客ID":"1001","顧客種別":"大口","注文上限":200}}}},
     {"insert":{"object":{"com.company.model.注文":{"注文ID":"O-501","顧客ID":"1001","商品ID":"RA29001","加工種別":"カット大","注文数量":201}},"out-identifier":"注文結果"}}, 
     {"fire-all-rules":""} 
   ] 
 }' 'http://localhost:8090/rest/server/containers/instances/business-application-kjar-1_0-SNAPSHOT'

今度は、大口顧客で、かつ注文上限数を超えた注文数になっています。
- 顧客種別が"大口"かつ、注文数量>注文上限
のルールに一致するはずです。

結果
{
  "type" : "SUCCESS",
  "msg" : "Container business-application-kjar-1_0-SNAPSHOT successfully called.",
  "result" : {
    "execution-results" : {
      "results" : [ {
        "value" : 1,
        "key" : ""
      }, {
        "value" : {"com.company.model.注文":{
  "注文ID" : "O-501",
  "顧客ID" : "1001",
  "商品ID" : "RA29001",
  "注文数量" : 201,
  "加工種別" : "カット大",
  "注文可否" : false,
  "コメント" : "注文数上限超過(大口)"
}},
        "key" : "注文結果"
      } ],
      "facts" : [ {
        "value" : {"org.drools.core.common.DefaultFactHandle":{
  "external-form" : "0:2:1704910627:1355438064:3:DEFAULT:NON_TRAIT:com.company.model.注文"
}},
        "key" : "注文結果"
      } ]
    }
  }

さて、予想通り、注文可否=false、コメントに「注文数上限超過(大口)」とセットされていますね。
"大口顧客の注文数量上限チェック"ルールが動いたことがわかります。

rule "大口顧客の注文数量上限チェック"
    when
        注文者(顧客種別 == "大口", $ID:顧客ID, $注文上限:注文上限)
        $注文:注文(顧客ID == $ID, 注文数量 > $注文上限)
    then
        modify($注文) {
            set注文可否(false),
            setコメント("注文数上限超過(大口)")
        }
end
Swagger UIを使ったルール実行

curlコマンドではなく、Swagger UIを使ったルール実行も同じように可能です。
Swagger UIはhttp://localhost:8090/rest/api-docs?url=http://localhost:8090/rest/swagger.jsonで開きます。
コンテナIDと、パラメータ部分を入力して、「Try it out!」をクリックします。

Response Body部分に、結果が表示されます。

curlコマンド実行時にorg.kie.server.api.marshalling.json.JSONMarshallerのエラーになる場合

curl実行時に、-dでjson形式のパラメータをつけた際、Internal Server Error 500で、以下のようなエラーが出る場合があります。

curl実行結果
{"timestamp":"2020-05-xxT07:00:06.636+0000","status":500,"error":"Internal Server Error","message":"org.kie.server.api.marshalling.json.JSONMarshaller$3.idResolver(Lcom/fasterxml/jackson/databind/cfg/MapperConfig;Lcom/fasterxml/jackson/databind/JavaType;Ljava/util/Collection;ZZ)Lcom/fasterxml/jackson/databind/jsontype/TypeIdResolver;","path":"/rest/server/containers/instances/business-application-kjar-1_0-SNAPSHOT"}
コンソールログ
Caused by: java.lang.NoSuchMethodError: org.kie.server.api.marshalling.json.JSONMarshaller$3.idResolver(Lcom/fasterxml/jackson/databind/cfg/MapperConfig;Lcom/fasterxml/jackson/databind/JavaType;Ljava/util/Collection;ZZ)Lcom/fasterxml/jackson/databind/jsontype/TypeIdResolver;

これは、KieServer側のバグで、すでに報告済みなのでそのうち修正されるはずなんですが、とりあえず現時点では、spring-boot-startarのバージョンを少し古いものに変えると、これを回避できます。

<business-application>-service/pom.xmlの以下の箇所を修正します。

pom.xml
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <!--<version>2.2.2.RELEASE</version>-->
    <version>2.0.9.RELEASE</version>
  </parent>

(ちなみに、私は製品側のバージョン: 7.33.0.Final-redhat-00002、spring-boot-startar :2.2.2.RELEASE でやると、このエラーになりました)

はい、以上です。