KotlinでDroolsを動かす

需要があるかわからないですが、業務で使用したので備忘までに書いておきます。

# Droolsとは
オープンソースのBRMS(Business Rule Management System)。Redhat製。

BRMSを使うことで、ビジネスルール(if文の分岐)をアプリケーションから外出しにすることが出来ます。
DroolsではDRLと呼ばれる独自言語やExcelでビジネスルールを記述します。

# Kotlinで動かす
DroolsはJavaのライブラリが提供されているので、Kotlinでも動かすことが出来ます。
サンプルコードは[GitHub](https://github.com/keitaro1020/kotlin-drools-sample)にあげてあります。

## 実装内容

りんごの仕分け（ランク付け）を行います。
りんごには重さがあって、重さによってランクをS、A、B、C、Dの５段階に分けるというルールをKotlinとDroolsを使って実装していきます。

### 環境
* Kotlin: 1.2.21
* Gradle: 4.3.1
* Drools: 7.5.0.Final
* IntelliJ IDEA

### gradleの設定
dependenciesに以下の2行を追加します

```gradle
    compile "org.kie:kie-api:7.5.0.Final"
    compile "org.drools:drools-decisiontables:7.5.0.Final"
```

### アプリの実装
りんごはKotlinのdata classを使って実装します

```kotlin
data class Apple(val size: Long, var rank: String = "")
```

ルールを実行するルールエンジンの取得。実装は[こちらの記事](https://qiita.com/madilloar/items/0de75c433bfde5d3baeb)を参考にさせて頂きました。

```kotlin
fun getKieSession(filepath: String) : KieSession? {

    val kieServices = KieServices.Factory.get()
    val kieFileSystem = kieServices.newKieFileSystem()

    val file = Paths.get(filepath).toFile()

    // (debug) xlsをdrl形式で表示する
    file.inputStream().use {
        val sc = SpreadsheetCompiler()
        println(sc.compile(it, InputType.XLS))
    }

    var kieSession: KieSession? = null

    file.inputStream().use {
        // inputStreamからの読み込み
        kieFileSystem.write(
                "src/main/resources/rules.xls",
                kieServices.resources.newInputStreamResource(it)
        )
        val kieBuilder = kieServices.newKieBuilder(kieFileSystem).buildAll()

        // ルールファイル記述内容のチェック
        val results = kieBuilder.results
        if (results.hasMessages(Message.Level.ERROR)) {
            println(results.toString())
            throw IllegalStateException(">>>ルールファイルの記述に問題があります。")
        }

        kieSession = kieServices
                .newKieContainer(kieServices.repository.defaultReleaseId)
                .newKieSession()
    }

    return kieSession
}
```

取得したルールエンジンを元にルールを実行します。

```kotlin
fun main(args: Array<String>) {

    val filepath = if(args.isNotEmpty()) {
        args[0]
    } else {
        throw IllegalArgumentException("ルールファイルが指定されていません")
    }

    val apples = listOf(
            Apple(size = 1),
            Apple(size = 5),
            Apple(size = 10),
            Apple(size = 30),
            Apple(size = 70),
            Apple(size = 120)
    )

    val kieSession = getKieSession(filepath)
    kieSession?.let {

        apples.forEach { apple ->
            it.insert(apple)
            it.fireAllRules()    // ルール実行

            println("Size: ${apple.size} -> Rank: ${apple.rank}")
        }

        it.dispose()
    }
}
```

### ルール定義
Excelを使ってルールを記述します。

ルールエンジンで使用するclassをimportします。
<img width="913" alt="スクリーンショット 2018-01-25 13.13.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/126014/452a2c68-f711-5db2-1281-73da77eda0eb.png">

CONDITIONの列に判断条件を書き、ACTION列に条件に合致した場合に実行する処理を書きます。
今回の場合、サイズが
* 100以上 -> Rank S
* 99〜50 -> Rank A
* 49〜10 -> Rank B
* 9〜5 -> Rank C
* 5未満 -> Rank D
というルールになります。
<img width="910" alt="スクリーンショット 2018-01-25 13.13.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/126014/3b8b494c-0fbb-77c6-2eaa-5652aec7a65b.png">

CONDITIONの`eval($param)`という記述はCONDITION列のコード（`100 > size && size >= 50`とか）をJavaのコードとして解釈し実行してくれるものです。

### 実行してみる
```shell
$ ./gradlew run -Pargs="data/rules1.xls"

(省略)

Size: 1 -> Rank: D
Size: 5 -> Rank: C
Size: 10 -> Rank: B
Size: 30 -> Rank: B
Size: 70 -> Rank: A
Size: 120 -> Rank: S
```

サイズごとにランクをつけて返してくれました。

ルールを↓のように変更して実行すると、
<img width="910" alt="スクリーンショット 2018-01-25 13.25.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/126014/6771e967-4a3f-cdf6-e3de-208310e47ec7.png">

```shell
$ ./gradlew run -Pargs="data/rules2.xls"

(省略)

Size: 1 -> Rank: D
Size: 5 -> Rank: D
Size: 10 -> Rank: D
Size: 30 -> Rank: D
Size: 70 -> Rank: C
Size: 120 -> Rank: A

```
ルールの変更によって結果が変わったことがわかります。

# まとめ
~~このくらいの条件ならコードにif文を書けばいいじゃないか~~
BRMSを使用することでビジネスルールをコードと分離でき、運用側・業務側でルールを変更することが可能となり、仕様変更に柔軟な対応を行うことが出来ます。
BRMS製品の一つであるDroolsはJavaのライブラリが提供されており、KotlinでもDroolsを利用したアプリを作成することが可能です。