Drools version 8 でサンプルアプリを作ってみる


Droolsに業務でふれる機会があったため、理解の定着のため簡単なアプリ作成をしていきたいと思います。

## Droolsとは

Javaで構築されているOSSのルールエンジンです。
ルールエンジンとは、事前に定義したルールをもとに入力データに対して一連の処理をする仕組みのことです。
入力データの内容をチェックし、「データが〇〇であれば、△△する」といったルールを順次適用していくことができます。
ルールに基づいた意思決定(データの作成・更新など)を自動化することが可能です。

2023年5月時点でも開発が活発にすすんでおり、公式ページによるとDrools version 8が最新版となっています。
ただ、日本では結構マイナーな技術なのか、日本語で書かれた記事やリファレンス実装が少ないように感じています。。
(一応公式のGithubに[実装例](https://github.com/kiegroup/drools/tree/main/drools-examples)があるのですが、依存モジュールが多く、どのモジュールが何をしているのか分かりづらい。。)

本記事では、最小構成のサンプルアプリをどう作るかを模索していきたいと思います。

## サンプル題材

飲食店において、顧客の年齢に応じて提供するドリンクを決定するアプリケーションについて考えてみましょう。
業務ルールを以下のように定義します。

|ルール|顧客|ドリンク|
|---|---|---|
|年齢が20歳以上ならビール|年齢が20歳以上|ビール|
|年齢が20歳未満ならジュース|年齢が20歳未満|オレンジジュース|

こちらをDroolsの仕組みを使って実装していきます。
アプリの技術要素としては、以下を採用します。

- Java言語(jdk version 17)
- gradle 7.6

コード全量は[Github](https://github.com/Hami1ton/study_brms/tree/main/drools/execute_drl_drools)にあげています。

## 実装

### 前提

まず、Droolsでのルールの管理・実行について補足しておきます。
Droolsのドキュメントなどを見ると、頭に「Kie」とついているコンポーネントがよく登場します。
※ Kie・・・Knowledge is Everythingからきているとか。かっこいいですね。

そのうちKieSessionと呼ばれるものが、ルールエンジンとルールエンジンの利用側との橋渡しの役割を果たします。
利用側は、KieSessionに対してデータの入力やルール実行の命令などを行うことができます。
また、KieContainerと呼ばれる要素もあり、こちらはルールエンジンに定義されたルールが保管されます。さらに、KieSessionを作成する役割ももっています。

```mermaid
flowchart TB
  User-->|Insert Data / Execute Rule|kSession[KieSession]
  kSession--> |Use Rules|kContainer[KieContainer]
  kContainer--> |Create Session|kSession
```

厳密にはKieContainerの中にKieModule, KieBaseといった要素があり、それぞれが仕事をしているようです。
が、詳細はDroolsの公式ドキュメントを参照していただくとして、ここまでを前提としてコードを書いていきましょう。

### 依存関係の追加

Drools関連のライブラリを依存関係に記載しておきます。

```build.gradle
ext {
    droolsVersion = '8.39.0.Final'
}

dependencies {
    // drools
    implementation group: 'org.drools', name: 'drools-mvel', version: droolsVersion
    implementation group: 'org.drools', name: 'drools-xml-support', version: droolsVersion

...
```

### データクラスの定義

ルールエンジンに操作させるデータを、POJOとして定義します。

```Person.java
package org.example;

public class Person {

    private String name;

    private int age;

...
```

```Drink.java
package org.example;

public class Drink {

    private String name;

    private int charge;

...
```


### ルールの定義

Droolsに読み込ませるルールを、DRL(Drools Rule Language)ファイルに記載します。
DRLはルールを記述するための特別な文法で、「〇〇のとき(when)、△△する(then)」のようにルールをwhen/thenの形式で記述できます。

```Drink.drl
import org.example.Person
import org.example.Drink

rule "Child"
    when
        $person : Person( age < 20 )
        $drink : Drink()
    then
        $drink.setName( "Orange Juice" );
        $drink.setCharge( 100 );
end

rule "Adult"
    when
        $person : Person( age >= 20 )
        $drink : Drink()
    then
        $drink.setName( "Beer" );
        $drink.setCharge( 200 );
end

```

DRLは特別なプログラムというわけではなく、内部的には最終的にJavaコードに変換されて扱われます。
※ 本ファイル内にJavaのコード(`System.out.println()`とか)を書くこともできます

`drools-mvel`を依存ライブラリに追加しているので、DroolsがDRLファイルを解析してくれます。


### KieContainer作成準備

KieContainerを生成できるように、kmodule.xmlというファイルを作成しておきます。
こちらはリソースパス(src/main/resources/META-INF)配下に配置します。

```kmodule.xml
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
</kmodule>

```

`drools-xml-support`を依存ライブラリに追加しているので、Droolsがこのファイルを読み込んでKieContainerを作成してくれます。


### KieSessionの作成とルール実行処理の記載

ここからルール実行のためのMainクラスを書いていきます。

```Main.java
public static void main(String[] args) {

        // set up
        var person = new Person();
        person.setName("Taro");
        person.setAge(18);
        var drink = new Drink();

        KieServices ks = KieServices.Factory.get();
        KieContainer kieContainer = ks.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession();
        kieSession.insert(person);
        kieSession.insert(drink);

        // exectute rule
        kieSession.fireAllRules();

        System.out.println(drink);

        // dispose
        kieSession.dispose();

    }
```

流れとしては、作成したKieContainerからKieSessionを作成し、その後、ルールで操作させたいデータをSessionに投入しています。
`fireAllRules`メソッドを呼び出すことで、ルールが実行されます。
(Drinkインスタンスのname項目は、`fireAllRules`が呼ばれる前では空になっています。)

ここでは18歳の太郎君を投入しているので、ルールが発火するとオレンジジュースが提供されるはずです。
最後にSessionをdispose(破棄)します。
※ disposeしないと、KieSession内のデータが残留してメモリリークの問題につながるため、必ず実行したほうがよいとのことです。


### アプリ実行

Mainクラスを実行時のログは以下のようになります。
投入したDrinkクラスの値に、オレンジジュースが詰められていることがわかります。

```
[main] INFO org.drools.compiler.kie.builder.impl.InternalKieModuleProvider - Creating KieModule for artifact org.default:artifact:1.0.0
[main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - Start creation of KieBase: defaultKieBase
[main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - End creation of KieBase: defaultKieBase
name: Orange Juice, charge: 100

BUILD SUCCESSFUL in 12s
```


## 最後に

読んでいいただきありがとうございました。

本記事程度のルールであれば、Javaの単純なif分岐で十分な気がしますし、設定用ファイルが多くてあまりDroolsの良さが感じられないかもしれません。
ただ、ルールが今後複雑になったときにDroolsを使っていれば、あちこちに業務ロジックが記載されプログラムがカオスになることを防げると考えております。
(複雑な業務ロジックは、drlファイルの中に閉じ込めることができます)

いろいろネットの情報をあさりながらなんとか動かしているので、もっといいやり方があるかもしれません。
もしご指摘などありましたら、コメントいただけると嬉しいです。
(お手柔らかにお願いします :bow: )

## 参考リンク

- Drools公式
    - [Home Page](https://www.drools.org/)
    - [Document 8.39.0.Final](https://docs.drools.org/8.39.0.Final/drools-docs/docs-website/drools/introduction/index.html)
    - [Github](https://github.com/kiegroup/drools)
- 本記事で紹介したアプリケーションのコードは[こちら](https://github.com/Hami1ton/study_brms/tree/main/drools/execute_drl_drools)