前編では、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
配下に、注文者クラスと注文クラスを新規追加しましょう。
package com.company.model;
import lombok.Data;
@Data
public class 注文者 {
private String 顧客ID;
private String 顧客種別;
private int 注文上限;
}
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で実装すると、こんな感じになります。
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 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を追加します。
例
<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 -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 -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で、以下のようなエラーが出る場合があります。
{"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
の以下の箇所を修正します。
<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 でやると、このエラーになりました)
はい、以上です。