Kotlin
Drools
BRMS

KotlinでDroolsを動かす

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

Droolsとは

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

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

Kotlinで動かす

DroolsはJavaのライブラリが提供されているので、Kotlinでも動かすことが出来ます。
サンプルコードはGitHubにあげてあります。

実装内容

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

環境

  • Kotlin: 1.2.21
  • Gradle: 4.3.1
  • Drools: 7.5.0.Final
  • IntelliJ IDEA

gradleの設定

dependenciesに以下の2行を追加します

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

アプリの実装

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

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

ルールを実行するルールエンジンの取得。実装はこちらの記事を参考にさせて頂きました。

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
}

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

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します。
スクリーンショット 2018-01-25 13.13.01.png

CONDITIONの列に判断条件を書き、ACTION列に条件に合致した場合に実行する処理を書きます。
今回の場合、サイズが
* 100以上 -> Rank S
* 99〜50 -> Rank A
* 49〜10 -> Rank B
* 9〜5 -> Rank C
* 5未満 -> Rank D
というルールになります。
スクリーンショット 2018-01-25 13.13.27.png

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

実行してみる

$ ./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

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

ルールを↓のように変更して実行すると、
スクリーンショット 2018-01-25 13.25.52.png

$ ./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を利用したアプリを作成することが可能です。