アシストの製品サービスの紹介や課題解決、イベント・セミナー情報をお届けします。

No.10 Corticonの【拡張演算子】と【サービスコールアウト】を使ったルール実装のしかた (2016年12月22日)


Corticonに限らずBRMS製品でルールを実装していると、デシジョンテーブル形式ではどうしても書きにくい処理や機能が無くて実現できない処理があり、『プログラミング言語で書けたなら』と思うことがあります。例えば、データの処理順序を意識したアルゴリズムや配列処理、日本特有の文字列処理やカレンダー処理、外部リソースへの入出力やOSへのコマンド実行などがあります。通常はそのような処理はBRMSへデータを渡す前かBRMSがデータを処理した後に実行しますが、実際はそこまで綺麗に切り分けられないこともあります。そのような要望に対して、Corticon では【拡張演算子】および【サービスコールアウト】機能があり、それぞれの機能を使うとデシジョンサービスの処理中に任意のJavaプログラムを実行することができます。

【拡張演算子】と【サービスコールアウト】

【拡張演算子】と【サービスコールアウト】共通の特徴として、どちらもEclipseのPluginとしてjarを作成し、Corticon Studioのpluginsフォルダに組み込んで使います。どちらも任意のJavaプログラムを実装できますが、Eclipse Pluginとしての制約やCorticonとの連携部分である引数と戻り値の制約があります。作成した【拡張演算子】や【サービスコールアウト】をCorticon Serverで実行するには、Eclipse Pluginとして作成したjarをアプリケーションサーバのWEB-INF/libフォルダ(Tomcatの例)に配置します。

それぞれの作成方法などの詳細は製品マニュアル「Corticon Studio: Extensions Guide」に記載されています。

【拡張演算子】と【サービスコールアウト】の実装の具体例

■拡張演算子

ここではサンプルとして、日付データから休日や祝日を判定する【拡張演算子】を作成しました。
作成した【拡張演算子】「getJapaneseHolidayName」のソースコードはつぎのとおりです。

package com.acme.extended.operators;


import java.util.Date;
import com.corticon.services.extensions.ICcDateTimeExtension;
import ajd4jp.*;


/**
 * This is the Japanese holiday calendar class for extended operators of Corticon.
 * This uses "AJD4JP" library. http://ajd4jp.osdn.jp/
 * 
 * @author Ashisuto
 */
public class JapaneseHoliday implements ICcDateTimeExtension{


	/**
	 * This method return the Japanese holiday name of the given date.
	 * '平日', '休業日', '年末年始休業日', '祝日名'.
	 * 
	 * @param aDate
	 * @return JapaneseHolidayName
	 */
	public static String getJapaneseHolidayName(Date aDate) {
		try {
			if (aDate == null) {
				return "";
			}
			// ベース(土日休業)
			OffProvider MyCompanyOff = new OffProvider(true, Week.SATURDAY, Week.SUNDAY);


			// 年末年始休暇追加
			AJD from = new AJD(2000, 12, 29);
			AJD to = new AJD(2000, 1, 3);
			MyCompanyOff.addOffEveryYear(from, to, new NewYear());


			// 引数判定
			AJD day = new AJD(aDate);
			if (day.getYear() <= 1970 || day.getYear() >= 2030) {
				return "年の値が判定範囲を超えています";
			}
			OffProvider.Off off = MyCompanyOff.getOff(day);
			if (off == null) {
				return "平日";
			} else {
				if (off instanceof Holiday) {
					return ((Holiday) off).getName(day);
				}
				if (off instanceof Week) {
					return "休業日";
				}
				return off.toString();
			}
		} catch (Exception e) {
			return e.getMessage();
		}
	}


	private static class NewYear implements OffProvider.Off {
		public String toString() { return "年末年始休業日"; }
	}
}

このプログラムでは「AJD4JP」という日本の祝日を算出するオープンソースのライブラリを使います。したがって、このソースコードをEclipse Pluginとしてビルドする際は、AJD4JPライブラリのjarファイルをPluginのjar内に含める必要があります。また、Corticon Serverで実行するにはCorticon Serverのlibフォルダに、作成したPluginのjarファイルとAJD4JPライブラリのjarファイルを配置する必要があります。

つづいて、この【拡張演算子】「getJapaneseHolidayName」を使ったつぎのルールを作成します。「属性_1」は日付型、「属性_2」は文字列型の語彙属性です。

下図のようにCorticon Studioのルール演算子の箇所に、実装した拡張演算子名「getJapaneseHolidayName」を表示させるには、【拡張演算子】(JARファイル)内の「ExtendedOperators.operationsmodel」ファイルに必要項目を記述するのですが、詳細は割愛します。ルール演算子の一覧にメソッド名を表示させなくても実装した【拡張演算子】は使えます。

このルールを実行すると動作はつぎのようになります。

この【拡張演算子】で必要なのは、“属性_1”の日付型の値だけなので、他のルールや他の語彙でも日付型の属性に対して汎用的に使えます。

また、この【拡張演算子】はCorticon外部への入出力が一切ありませんが、もし【拡張演算子】から外部リソースへの入出力を行う場合は、複数同時に実行されてデッドロックやコンフリクトを起こさないように注意します。

なお、似たような処理は、Corticonのデシジョンテーブル上のルールでも記述することができます。デシジョンテーブル上のルールとして記述する方法と【拡張演算子】を使う方法の特徴を比較するとつぎのようになります。

●ルールとして記述する方法

 -休日や祝日の判定をデシジョンテーブル上のルールとして記述する。

 -土日などの定休日に関しては標準のルール演算子「dayOfWeek」などを利用すると簡単に表現できるが、祝日に関しては網羅的に記入したりかなり複雑な計算を行う必要がある。

 -メリット

  ・休日や祝日が変更になった場合は、デシジョンテーブルの変更だけで対応できる。

 -デメリット

  ・ビジネスルール以外のロジックがデシジョンテーブルに加わる。

●【拡張演算子】として実装する方法

 -休日や祝日を判定するJavaプログラムを実装し、【拡張演算子】としてルールから呼び出す。

 -メリット

  ・その日が休日や祝日かをデシジョンテーブルで簡潔に記述できる。

 -デメリット

  ・休日や祝日が変更になった場合は、プログラムの変更が必要になる。

メリットとデメリットはトレードオフの関係で、どちらが良いかはケース・バイ・ケースです。

■【サービスコールアウト】

ここではサンプルとして、DBに接続し任意のSQL(SELECT文)を実行して取得したデータを語彙に展開する【サービスコールアウト】を作成しました。

通常、CorticonでDB内のデータを利用する場合、つぎのどちらかの方法になります。

  • クライアントアプリケーション側でDBにアクセスし必要なデータを取得した後にSOAPリクエストの要素としてCorticonに送信する。

  • CorticonのEDCオプション(※)を使ってルール中でDBデータを取得する。

【サービスコールアウト】機能を利用すると、これらの手段に頼らなくても任意のデータベースから
取得したデータをCorticonのデシジョンサービス内で利用することができます。

※EDCについて
-EDCとはEnterprise Database Connectorの略で、対応するライセンスが必要なCorticonのオプション製品です。
ルール実行中に外部のデータベースにアクセスしデータを取得する機能を提供します。

EDCオプションと比較すると、今回作成した【サービスコールアウト】のサンプルには
つぎのような特徴があります。

  • EDCがサポートしないRDBMS(例えば今回のサンプルのSQLite)への接続が可能。

  • EDCでは設定不可能な、異種のRDBMSや複数のDBに対する接続が可能。

  • EDCでは記述不可能な、複雑なSQL文の実行が可能。


作成した【サービスコールアウト】「getSqliteTable」のソースコードはつぎのとおりです。

package com.corticon.operations.extended.examples.servicecallouts;


import java.sql.*;


import com.corticon.services.dataobject.ICcDataObject;
import com.corticon.services.dataobject.ICcDataObjectManager;
import com.corticon.services.extensions.ICcServiceCalloutExtension;


public class executeQueryBySC implements ICcServiceCalloutExtension {


	/**
	 * This is a ServiceCallout that retrieves data from the SQLite DB.
	 * It works only when the used Entity, Attribute, Association exists in the vocabulary of Corticon.
	 * 
	 * @param aDataObjMgr
	 */
	public static void getSqliteTable(ICcDataObjectManager aDataObjMgr) {
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;


		for (ICcDataObject t : aDataObjMgr.getEntitiesByName("DB_TABLE")) {
			try {
				Class.forName("org.sqlite.JDBC");
			} catch (ClassNotFoundException e) {
				t.setAttributeValue("ERROR", (String) e.getMessage());
				return;
			}


			String ConnectionURL = (String) t.getAttributeValue("ConnectionURL");
			String ExecuteSQL = (String) t.getAttributeValue("ExecuteSQL");


			try {
				conn = DriverManager.getConnection(ConnectionURL);
				stmt = conn.createStatement();
				rs = stmt.executeQuery(ExecuteSQL);


				while (rs.next()) {
					ICcDataObject r = aDataObjMgr.createEntity("ROW");
					if (r == null) {
						return;
					}
					r.setAttributeValue("NO", (long) rs.getRow());


					ResultSetMetaData meta = rs.getMetaData();
					for (int i = 1; i <= meta.getColumnCount(); i++) {
						ICcDataObject c = aDataObjMgr.createEntity("COLUMN");
						if (c == null) {
							return;
						}


						c.setAttributeValue("NAME", (String) meta.getColumnName(i));
						c.setAttributeValue("VALUE", (String) rs.getString(i));
						r.addAssociation("COLUMNS", c);
					}
					t.addAssociation("ROWS", r);
				}


				rs.close();
				stmt.close();
				conn.close();


			} catch (SQLException e) {
				t.setAttributeValue("ERROR", (String) e.getMessage());
				return;
			}
		}
	}
}

このソースコードをEclipse Pluginとしてビルドする際は、SQLiteのJDBCドライバをjar内jarとして作成したPluginに含める必要があります。また、Corticon Serverで実行するには、Serverのlibフォルダに作成したPluginのjarファイルとSQLiteのJDBCドライバをlibフォルダに配置する必要があります。SQLite以外のRDBMSでもほぼ同様の実装で動作しますが、その場合はそれぞれのDBに対するJDBCドライバを用意する必要があります。

ソースコードからも確認できますが、この【サービスコールアウト】は引数で与えられる語彙オブジェクトに依存しています。この【サービスコールアウト】を実行するルールフローの語彙(ecoreファイル)で、つぎのようなエンティティと属性、関連性が作成されていることを前提にしています。もし語彙に必要なエンティティ, 属性, 関連性が語彙(ecoreファイル)に無い場合は【サービスコールアウト】は動作しません。

前提となる語彙構造はなるべく汎用的に使えるように抽象的な構造にしています。このような構造であれば任意のDBテーブルデータを格納することができますが、もし特定のDBテーブルだけが対象で【サービスコールアウト】を使うのも特定のルールフローだけ、ということであればもっと具体的な構造にすることもできます。

次に、この語彙と【サービスコールアウト】を使ったルールフローを作成します。

Corticon Studioのルールフロー画面の【サービスコールアウト】で、実装したサービス名を表示させるには、【拡張演算子】(JARファイル)内の「ExtendedOperators.operationsmodel」というXMLファイルに必要項目を記入する必要がありますが、詳細はここでは割愛します。

このルールフローを実行するにあたり、例えばSQLiteでつぎのようなテーブル「t_test」を作成し、C:/Users/progress/Documents/test.dbに保存したとします。

ルールフローへの入力値としてDB_TABLEのConnectionURLに「jdbc:sqlite:C:/Users/progress/Documents/test.db」、ExecuteSQLに「SELECT * FROM t_test;」を与えます。
実行すると、つぎのようになります。テーブル内の全データがROWSとCOLUMNSとして展開されます(図では折畳まれている箇所にも正しいデータが入っています。)。【サービスコールアウト】のプログラムとCorticonの語彙の実装上の都合で、DBテーブル内で整数型や浮動小数点のデータはCorticonには全て文字列型として格納されます。

ExecuteSQLのSQL文を一行一列しか取得しないような「SELECT column3 FROM t_test WHERE pkey=3;」に変更すると結果はつぎのようになります。

『ConnectionURL』や『ExecuteSQL』属性の内容を誤った値にした場合は『ERROR』属性に実行時のエラーメッセージが格納され結果は一件も出ません。

この【サービスコールアウト】を実際のDBやルールで使う場合はつぎのような注意点があります。

  • Corticonの語彙データに新規エンティティを作成する処理速度は、EDCよりも遅いため、取得結果のデータ量が多くなるSQLの場合は処理速度について注意が必要。

  • 実行するルールフローのecoreファイルに対応している語彙構造を作らないといけないため、
    EDCほどの汎用性はない。

  • DBへのSQLやDBから取得したデータの構造はどうしても複雑になるため、この【サービスコールアウト】を前提としたルールも複雑になる可能性がある。ルール中ではなくプログラム中でSQLを生成するか、ルールやDB固有の語彙構造とすると、簡潔なデシジョンテーブルを保つことができるが、【サービスコールアウト】の汎用性は損なわれるため、どちらが良いかはケース・バイ・ケース。

  • 今回はSELECT文なので問題ないが、INSERT, UPDATE, DELETEの場合は、デシジョンサービスが複数同時に実行されてコンフリクトすることも考え、適切なトランザクション処理を実装する必要がある。

■まとめ

今回はCorticonの【拡張演算子】と【サービスコールアウト】のサンプルを作成しました。【拡張演算子】と【サービスコールアウト】の一般的な特徴をまとめるとつぎのようになります。

●共通の特徴

 -Eclipseのプラグインとして作成する。

 -Corticon Serverの標準の動作はスレッドセーフで処理中のデシジョンサービスが他の処理中のデシジョンサービスに影響を与えないが、これらの機能で外部リソースへの入出力を行うとスレッドセーフではなくなる可能性がある。

●【拡張演算子】

 -標準的なルール演算子と同様にルールシート上に記述し、ルール処理が記述箇所に入ったタイミングでプログラムが呼び出される。

 -プログラムの引数は呼び出された際の語彙の属性の値であり、プログラムが依存するのはその属性の型だけなので、汎用性が高く他のルールフロー(語彙)でも再利用しやすい。

 -Corticonにある標準の各種ルール演算子では足りないものを追加するときに便利。

 -【拡張演算子】を記述したルールを通るデータ量が多く、何回もその【拡張演算子】が呼び出されるような場合は、処理速度について注意が必要。

●【サービスコールアウト】

 -標準的なルールシートと同様にルールフロー上に要素として配置し、フロー処理が配置箇所に入ったタイミングでプログラムが呼び出される。

 -プログラムの引数は【サービスコールアウト】を呼び出した際のフロー処理中の語彙データ全体であり、プログラムは語彙の構造に依存するため、汎用的に作成することが難しい。

 -語彙データ全体に関わりデシジョンテーブルでは表現できない/しにくい処理を実装するのに便利。

 -フロー上で処理されるため呼び出される回数は少ないものの、入力/出力すべきデータが多い場合は、処理速度について注意が必要。

Corticonでデシジョンサービス処理中になんらかのプログラムを呼び出したい場合は、これらの特徴踏まえて【拡張演算子】や【サービスコールアウト】機能を活用してください。

ページトップへ戻る


「Corticon Tech コラム」記事一覧

「Corticon Tech コラム」記事一覧はこちらから

ページトップへ戻る


この記事をシェアする

おすすめ記事

この記事のハッシュタグ #Progress Corticon #Corticon Tech コラム から関連する記事を表示しています。