Rust製のルールエンジン「ZEN Engine」をさわってみる

OSSのルールエンジンでRustコードから呼び出しやすいものがないか探していたところ、ZEN Engine(以下、ZEN)というものに出会いました。
[公式のGithub](https://github.com/gorules/zen)を参考に軽くさわってみたので、紹介していきたいと思います。

[ZENとは](#zenとは)
[サンプルコード](#サンプルコード)
[Droolsとの違い](#droolsとの違い)
[参考](#参考)


## ZENとは

Rustで実装されたOSSのルールエンジンです。
GoRulesというコミュニティによって開発されているようです。
2023年8月現在、MITライセンスで公開されています。
Rustだけでなく、PythonやNodeJSのアプリからもライブラリとして利用できるようです。
ルールエンジンに実行させたいルールは、JSON Decision Model というJSONファイル(以下 JDMファイル)の形式で記述します。

## サンプルコード

実際にZENを使ってルールを実行するサンプルコードを書いていきたいと思います。

### サンプル題材

過去記事と同じ題材ですが、、年齢に応じて飲み物を決定するルールを題材とします。

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


### 公式のエディターでルールを作成する

先述したように、ZENにロードして実行するためのルールは、JDMというJSONファイルとして記述します。
GoRulesの公式サイトをみると、JDMファイルを簡単に作成するためのエディターが公開されています。

https://editor.gorules.io/

エディターを開くとキャプチャのような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/4e5f0f84-0efc-da7e-80f5-2cf7e9099aa8.png)

「Edit Graph」を押すと、ルールのInput, Output そしてDecision Table(決定表)を Drag & Dropで追加できます。
ほかにもFunction, Expressionという要素もありますが、ここでは割愛します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/412795b4-9d43-b1f3-7535-b978ace0f706.png)


「Confirm」ボタンを押すと、「decisionTableNode」に「Open」ボタンが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/70891a78-6eb3-bb46-e43d-d34bb6728b8e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/431bcbe6-5e87-74ec-0763-d3c5b36ab480.png)

「Open」ボタンを押すと、Decision Tableを編集するためのタブが表示されます。
このタブ上で、先ほど記載したドリンク決定のためのルールを記述していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/d6c0d44c-cbef-46f7-f125-076b20ea3d0f.png)


ルールの記述が完了しました。
Simulatorを開いてルールの動作検証をすることもできます。
発火したルール行がハイライトされるのでわかりやすいですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/47e779bc-efe7-7014-456f-c2369fcc5496.png)


最後に、「Download json」でJDMファイルをダウンロードできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481175/1a89af16-661f-e3f5-9d33-aae6bdb0a972.png)

実際に作ったJDMファイルは[こちら](https://github.com/Hami1ton/study_brms/blob/main/gorules/zen_engine_example/src/DrinkRule.json)。

### Rustアプリにルールエンジンを組み込んで実行する

作成したJDMファイルを、Rustコード上でロードして実行していきます。
cargoに必要な依存関係を追加していきます。

```Cargo.toml
[dependencies]
zen-engine = "0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

ルールを記述したJDMファイルのロードと、ルールの実行箇所は以下のようになります。
```main.rs
async fn evaluate() {
    // load JDM
    let decision_content: DecisionContent = serde_json::from_str(include_str!("DrinkRule.json")).unwrap();
    
    let engine = DecisionEngine::default();
    let decision = engine.create_decision(decision_content.into());
  
    // execute rule
    let result = decision.evaluate(&json!(
        {
            "person": {
                "age": 21
            }
        })).await;

    println!("Rule Result = {:?}", result);

}
```

cargo runでRustコードを実行すると、以下のメッセージが出力されます。
```
Rule Result = Ok(DecisionGraphResponse { performance: "274.4µs", result: Object {"drink": String("Beer")}, trace: None })
```

問題なくルールが実行されていますね。

## Droolsとの違い

公式のブログでDroolsとの比較記事がでていました。
Droolsと比べて、軽量さやシンプルさを打ち出しているようですね。

https://gorules.io/blog/gorules-vs-drools

これからも引き続き注視していきたいと思います。


## 参考

公式のWEBサイトです

https://gorules.io/

紹介したサンプルコードは[こちら](https://github.com/Hami1ton/study_brms/tree/main/gorules/zen_engine_example)