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

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

### 前編はこちら
まずは、[Red Hat Decision Managerのルール実行環境をSpringBootで素早く構築してルールアプリを動かしてみる（前編）](https://qiita.com/Erina/items/8282dc1980fc1b686f32)からどうぞ。

## （後編） ルールアプリを作成しKieServer上で実行する
次に、ダウンロードしたzipを解凍して出来た、３つのプロジェクトは、ほぼ中身が空ですので、ここにルールアプリを追加して動かしてみましょう。

#### プロジェクト構成

３つのプロジェクトを確認します。お好きなIDE上に展開してください。（IDE無くても良いですが、まあ使ったほうが便利かと）
<img width="343" alt="スクリーンショット.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276989/0d9a3441-5f32-e524-0a4a-64ac7a6328b5.png">

- \<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:注文者.java
package com.company.model;

import lombok.Data;

@Data
public class 注文者 {

    private String 顧客ID;
    private String 顧客種別;
    private int 注文上限;
}
```

```java:注文.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ファイルを新規作成します。

<img width="328" alt="スクリーンショット.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276989/5d77982c-f6c5-ebfa-5a09-9ad90107d813.png">

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

```text:注文可否判定.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`に以下のように追加します。

```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を追加します。

```xml: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にルールをデプロイして呼び出す](https://qiita.com/Erina/items/f134d69028389798306c#%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B))に少し解説していますので、こちらを参照ください。

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

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

```text: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始まりでもないので、ルールに一致しません。


```text:結果
{
  "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になっていることが確認できますね。

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

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

```text:結果
{
  "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!」をクリックします。

![スクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276989/3a041340-8f6d-1325-f1bf-3766867ef7a0.png)

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


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

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

```text: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"}
```

```text:コンソールログ
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`の以下の箇所を修正します。

```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　でやると、このエラーになりました）

はい、以上です。