【Drools】DSL機能を使ってルールの可読性を高める


## はじめに

Drools では、DRL (Drools Rule Language) 構文で記述したルールによって、入力データが特定の条件をみたしたときの処理を自動化できます。

以下は、人の年齢に応じてドリンクを決定するルールです。

```
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
```

when 節では入力データに関する条件が、then 節では条件をみたしたときに実行する処理が記述されています。
プログラミング言語にある程度慣れている人であれば、おおよそどんなことをやっているか判断できるでしょう。
しかし、プログラミング経験がない方にとっては、DRL を一目で理解することは難しいと思われます。

今回の記事では Drools の DSL 機能を使って、Drools のルールをよりわかりやすくする方法を紹介します。


## DroolsのDSL機能について

DSL (Domain Specific Languages) は、日本語ではドメイン固有言語、またはドメイン特化言語などと呼ばれます。
Java などのプログラミング言語と異なり、特定の目的を果たすことにフォーカスして設計される言語を意味します。
アプリの依存関係を定義する目的で利用される、Gradleのビルドスクリプト等が例として挙げられます。

Droolsでは、ルールの記述に用いるDSLをDSLファイルに、DSLを用いたルール定義をDSLRというファイルで記述できます。
DSL・DSLRファイルをDroolsに読み込ませると、内部的にDRLファイルに変換したうえで、ルールを実行することができます。

DSLファイルは、次のような形式で記述されます。
```
[when]{ルールの条件を表すフレーズ} = {DRL構文}
[then]{ルールのアクションを表すフレーズ} = {DRL構文}
```

ここで定義している{ルールの条件を表すフレーズ}・{ルールのアクションを表すフレーズ}を、DSLRファイルで利用することができます。
定義したフレーズが、プログラマーでない方にとってもわかりやすい表現になっていれば、ルールの可読性も向上することが期待されます。

## サンプルアプリ

例として、最初に紹介したドリンク判定のルールをDSLで書いて動かしてみます。
サンプルコードのプロジェクトはGithubにあげています。

https://github.com/Hami1ton/study_brms/tree/main/drools/dsl/dsl_sample

### 環境、ライブラリバージョン

- windows11
- openjdk 17
- Drools 8.44.0.Final

### データクラス

通常のDRLでルールを実行する場合と同様、データクラスを定義します。

```Person.java
public record Person(
    String name, 
    int age
){}
```

```Drink.java
public class Drink {

    private String name;

    private int charge;

    public String getName() {
        return name;
    }

    public int getCharge(){
        return charge;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setCharge(int charge){
        this.charge = charge;
    }

    @Override
    public String toString() {
        return "name: " + name + ", " + "charge: " + charge;
    }

}
```

Personクラスはイミュータブルでいいので、手軽にレコードクラスで書いてます。


### DSLファイル

条件、アクション部に用いるDSLフレーズを定義します。

```DrinkRule.dsl
[when]来店客の年齢が {age} 才未満 = $person : Person(age < {age})
[when]来店客の年齢が {age} 才以上 = $person : Person(age >= {age})
[when]ドリンクが注文される = $drink : Drink()

[then]"{name}"を提供する = $drink.setName( "{name}" );
[then]料金は {charge} 円 = $drink.setCharge( {charge} );
```

`{age}`, `{name}`などと書いている箇所で、DSLRに記載した値を受け取ることができます。


### DSLRファイル

DSLファイルに定義したフレーズを使って、ルールを記述していきます。

```DrinkRule.dslr
import org.example.dslsample.Person
import org.example.dslsample.Drink

expander DrinkRule.dsl

rule "Child"
    when
        来店客の年齢が 20 才未満
        ドリンクが注文される
    then
        "オレンジジュース"を提供する
        料金は 100 円
end

rule "Adult"
    when
        来店客の年齢が 20 才以上
        ドリンクが注文される
    then
        "ビール"を提供する
        料金は 200 円
end
```

DSLRをDRLへ変換するために、`expander`属性でDSLファイル名を指定しています。


### 動作確認

実際にルールを動かしてみます。
以下がテストコードの一部です。
呼び出し部分のJavaコードは、通常のDRLで記載したルールを動かす場合と変わりません。
```SampleDslTest.java
public class SampleDslTest {
    
    @Test
    public void test_ドリンク判定ルール_Child() {
        KieServices ks = KieServices.Factory.get();
        KieContainer kieContainer = ks.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession();

        // set up
        var person = new Person("太郎", 13);
        var drink = new Drink();
        kieSession.insert(person);
        kieSession.insert(drink);

        // execute
        kieSession.fireAllRules();

        // assert
        assertEquals("オレンジジュース", drink.getName());
        assertEquals(100, drink.getCharge());

        kieSession.dispose();
    }
...

```

テストを実行すると、今回は13歳の太郎君が来店したので、飲み物はオレンジジュースと判定されることがわかります。
import文などプログラムチックなものは残っているものの、かなりルールの可読性が向上したと感じます。

今回紹介したDSL機能は、業務担当者(※)と協力して、アプリに組み込むルールを作成する際に有用ではないかと思われます。

(※)ここではIT職以外の方で、特定の業務知識をもち、作成したアプリケーションで日々の業務を行う人を想定しています

業務担当者とルールを検討する、実装したルールをレビューしてもらう、といったことがDSL機能を使うことでやりやすくなるのでは、と考えています。

## 参考

Drools 公式ドキュメント

https://docs.drools.org/8.44.0.Final/drools-docs/drools/language-reference/index.html#_domain_specific_languages


通常のDRLを使ったルールの実行方法については、こちらもご参照ください

https://qiita.com/yagii/items/5c1ee8781dc16252d2a3