Camunda DMN Engineを使ってDMNを実行する


## DMN(Decision Modeling and Notation)とは

OMG(Object Managemnt Group)で規定されている意思決定のモデリング記法です。近年RPAのナレッジ部分における意思決定自動化手法として注目されています。

https://www.omg.org/spec/DMN/About-DMN/

DMNの最大の特長は人間が理解しやすいように記述した意思決定モデルをそのままコードで実行できることです。Camunda社の[DMNシミュレータ](https://camunda.com/dmn/simulator/)でDMNの動作をテストできます。このサンプルでは季節(Season)とゲスト数(Number of Guests)をもとに最適な料理(Dish)を決定し、さらにその料理と子連れの有無(Guests with children)から最適な飲み物(Beverages)を決定します。ここでいう「最適」とは、あらかじめ定められたロジック(企業や個人のナレッジ)にもとづくという意味です。DMNではこのロジックをディシジョンテーブルという表で与えます。詳しくは[こちら](https://docs.camunda.org/manual/7.9/reference/dmn11/decision-table/)をご覧ください。

試しにDecision TableをBeveragesにし、Guests with Childrenをtrue、SeasonをFall、How Many Guestsを2にして実行してみます。以下のように二つの飲み物Aecht Schlenkerla RauchbierとApple Juiceが出力されました。画面から、入力した条件に合う料理としてSpareribsが選ばれたことが分かります。Spareribsに合う飲み物として選ばれたのはドイツのビールAecht Schlenkerla Rauchbierです。Apple Juiceは子ども用ですね。これは入力された季節やゲスト数などをもとに、ディシジョンテーブルという「ナレッジ」から最適な飲み物を選択した結果です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185900/004d5a52-be8e-2c34-3e33-b70563a033b9.png)


このシミュレータの裏ではCamunda DMN EngineによってDMNが実行されています。今回はこのCamunda DMN Engineをローカルで動かしてDMNを実行します。

## DMNファイルの準備

今回は検証用のDMNとしてCamundaのサンプルDMNファイルを使います。[DMNシミュレータのページ](https://camunda.com/dmn/simulator/)の「Download DMN Table」からサンプルのDMNファイルをダウンロードしてください。DMNファイルを閲覧・編集するにはDMNデザインツールが必要です。個人的にはスタンドアロンで気軽に動かせる[Camunda Modeler](https://camunda.com/products/modeler/)をお勧めします。ちなみにDMNの実行だけであればデザインツールは必要ありません。

## Camunda DMN Engineの取得

Camunda DMN EngineはMaven Centralで公開されています。POMに以下の依存関係を追加してください。

```xml
<dependency>
    <groupId>org.camunda.bpm.dmn</groupId>
    <artifactId>camunda-engine-dmn</artifactId>
</dependency>
```

## Camunda DMN Engineを使ったDMNの実行

以下にDMNを実行するJavaコードのサンプルを記述します。

```java
// DMNファイルパスとDecision Idの設定
String dmnFilePath = "./simulation.dmn";
String decisionId = "beverages";

// 入力データの設定
Map<String, Object> input = new HashMap<String, Object>();
input.put("season", "Fall"); // 秋
input.put("guestCount", 2);  // ゲスト2名
input.put("guestsWithChildren",true); // 子連れ

// DMNエンジンの生成
DmnEngine dmnEngine = DmnEngineConfiguration.createDefaultDmnEngineConfiguration().buildEngine();

// Decisionの生成
DmnDecision decision = dmnEngine.parseDecision(decisionId,new FileInputStream(dmnFilePath));

// DMNの実行
DmnDecisionResult result = dmnEngine.evaluateDecision(decision,input);

// 実行結果の取得
for(Map<String,Object> entry : result.getResultList()) {
    System.out.println(entry.get("beverages"));
}
```

ここでdecisionIdに指定するのはディシジョンテーブルのIdで、Camunda Modelerで表示するディシジョンテーブル上では以下の場所に設定している値です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185900/5374a3d3-324c-1883-104b-6871c3291019.png)


入力には先ほどDMNシミュレータを実行した例と同じ値を与えています。上記のコードを実行すると、以下のようにDMNシミュレータと同じ結果が得られます。

```console
Aecht Schlenkerla Rauchbier
Apple Juice
```
### Outputの追加

ディシジョンテーブル「beverages」のアウトプットはbeverageだけなので、このディシジョンテーブルでは飲み物しか取得できません。しかしどうせなら選択された料理と飲み物の両方を取得したいですね。そこでディシジョンテーブルを編集してアウトプットを増やします。以下の例ではディシジョンテーブルの編集にCamunda Modelerを使っています。

以下のように、beveragesのoutputに一列追加します。そして、このディシジョンテーブルのinputの一つであるDishの値をそのままoutputにします。このoutput列の「desiredDish」はDishのExpression(変数名)への参照を意味しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185900/dac6188d-d29b-8c07-ab91-36597f1b3903.png)


ディシジョンテーブルのInput項目は、表示ラベルであるLabelと、値の変数名または式を設定するExpression、値のデータタイプを示すTypeを持ちます。Camunda ModelerではInputのヘッダをクリックしないとExpressionが表示されません。サンプルのDMNではDish列のExpressionはdesiredDishとなっており、他の列から参照する際はこちらの値を使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185900/f363aebb-da2f-1e42-e07c-1192caeadfd4.png)


続いて料理と飲み物の両方を表示できるように、上記Javaコードの「実行結果の取得」を以下のように書き換えます。

```java
// 実行結果の取得
for(Map<String,Object> entry : result.getResultList()) {
    System.out.println(entry.get("desiredDish") + " with " + entry.get("beverages"));
}
```

実行結果を以下に示します。料理と飲み物の両方を取得できるようになりました。

```console
Spareribs with Aecht Schlenkerla Rauchbier
Spareribs with Apple Juice
```