多くの開発者にとって、ルールエンジンと言うのは単なるバズワード、もしくはアーキテクチャの図上のブラックボックスです。遠くの方から恐れられたり、称賛されたりしていますが、理解されてはいません。ルールエンジンのような新しいパラダイムを受け入れる際には、技術のどうしようもないジレンマが存在します。
- 現実世界で直に接してみるまでは、技術をいつ使用するのか、どうやったらそれをうまく適用できるのかを知るのが難しい
- 実際に体験してみるための最も一般的な方法は、実際のプロジェクトで知らないテクノロジーを使用してみることだ
- 本番環境で、新しい技術を使用した経験を実際に得ることは、将来の仕事においてかけがえのない経験である。しかし一方、大きなリスクを背負うことになる
この記事では、私が金融サービス向けの市場ソリューションをサポートするためにルールエンジンとDroolsを実践した経験を共有し、ルールエンジンの便利な個所と、あなたが直面している問題に対してどのようにルールエンジンを適用するのが最良か、を理解する手助けとなることを目指しています。
なぜ注意を払うべきか?
読者の中には、ルールエンジンの使用を既に考えていて、それをうまく使うための実践的なアドバイスを探している方もいらっしゃるでしょう。パターンとアンチパターン、ベストプラクティスと落とし穴、などです。
その他の方々はルールエンジンの使用を考えていない、あなたが従事している仕事にどう適用できるのかよくわからない、もしくはルールエンジンの使用を考えたがそのアイデアを捨ててしまった、というところでしょう。ルールエンジンはビジネスロジックを外部化するためのパワフルな方法で、ビジネスユーザに力を与え、非常に多くの細かいビジネスルールとファクトが相互作用する中で、複雑な問題を解決することができます。
もしあなたが条件分岐を多用していて、その組み合わせを評価しようと試み、一つの問題を片づけるため深く入れ子になったロジックを書いていることに気づいたなら、これらは単に一種のもつれ合いであり、ルールエンジンがそれを解く助けになります。
いくつかのより複雑な金融サービスは、ルールアプローチで表現し直してみると、より理解しやすくなりました。手続き型の条件分岐ロジックを、Droolsのビジネスルールに変換する各ステップが、より多くの単純さと、より多くのパワーを顕在化させていくようでした。
最後に、もしあなたが上記のことを信じられないなら、このように考えてください。ルールエンジンはツールであり、ソフトウェア開発アプローチの異なる方法だと。ツールは強みと弱みを持っており、もしあなたがルールエンジンをすぐに活用することができなくとも、そのトレードオフを理解する役には立ち、将来その適用可能性を評価し、伝えることができるのです。
ルールエンジンの基礎
いつ、どうやってルールエンジンを使うのかをお話しする前に、ルールエンジンとは何かを説明するのは意義があります。
ルールエンジンとは何か?
ルールエンジンとは、その中核は、'ビジネスルール'を実行するためのメカニズムです。ビジネスルールは、ビジネスにおけるある種の決定を表した、単純なビジネス指向のステートメントです。多くの場合は、非常に単純なif/thenの条件分岐で表されます。
例えば、仮想的な保険システムのためのビジネスルールは恐らく、
- 車の色が赤
- 車がスポーツクラス
- ドライバーが男性
- ドライバーの年齢が16-25才の間
これらのビジネスルールは新しいものではありません。多くのビジネスソフトアプリケーションの中核となるビジネスロジックです。もしあなたが開発者なら、こうしたルールは要件のサブセットとして表現されているのを何度となく見たことでしょう。これらは、"月曜日は、3つ以上のご注文で20%のディスカウント"や"スーパースポーツ・バイクでは、16才の男性に保険を掛けることはできません"といった文章に似ています。
ルールエンジン間の主な違いは、こうしたルールがどう表現されるか、です。プログラムの中に埋め込まれる代わりに、これらはビジネスルールの書式に符号化されます。この書式はルールエンジンごとに違っています。
ルールエンジンは、何の制限も受けていません。ルールを管理するための、他のツールと一緒にリリースされることがよくあります。一般的なオプションとしては、生成、デプロイ、保存、バージョニング、そして個別もしくはグループでのルール管理などです。
ルールエンジンの一つの例は、最近JBossグループの傘下に入ったDroolsです。Droolsはフリーで利用でき、オープンソースで、良いコミュニティを持っています。そのため、ルールエンジンを最初に検討するには優れた選択肢ですし、この記事でも例として使用されます。他のルールエンジンについてさらに情報を得たい方は、サイドバーのその他のルールエンジンを見てください。
ルールエンジンはどのように動作するのか?
ルールエンジンの根本的な性質は、それを動作させるアルゴリズムから来ています。いくつかの単純な'ルールエンジン'は、手続き型のロジックをあなたが指定した順番で単に次々と実行するだけです。ほとんどのルールエンジンは、ファクトとルールを繋ぐために、ReteやTreat、Leapsといった洗練されたマッチングアルゴリズムを提供しており、どのルールがどの順番で実行されるべきかを決定します。
競合の多くと同様、DroolsはRete (Charles Forgyによって開発されたマッチングアルゴリズム) を使用しています。Reteの詳細を取り扱うのはこの記事の範疇から外れていますが、単純に言うとこうです。Reteは状態マシンのように、ルールからツリーを構築します。ファクトはルールへのパラメータとしてツリーのトップレベルノードへと入り、条件に合致するとツリーを降りていきます。ツリーの葉、つまりルールの結果に到達するまでそれは繰り返されます。
Reteの作者や他の開発者達はReteの成功を受けて、新しいアルゴリズムにより改善を試みました。その結果がTreatやLeaps、Rete II、Rete IIIであり、そのいくつかは他のルールエンジンにより使われています。
これらのマッチングアルゴリズムは、コードの切れ端を単純に、順番通りに実行するよりも複雑です。もし全てのファクトに対し全てのルールを順番に実行したいとしたら、ルールエンジンは遅いでしょう。ルールエンジンのアプローチには、それを使用しない時には存在しないオーバーヘッドが存在します。一方、マッチングルールがデータに対して行う作業が自明でない場合には、ルールエンジンが洗練されていれば、条件の共有などのメカニズムにより時間を短縮できます。条件の共有とは、複数のルール間で条件を共有することで、条件の評価がルールごとに毎回行われるのではなく、最初の一度だけで済むというものです。
例えば、あなたがもし教育スケジュールのシステムを作っているとして、生徒のクラス分けを行うとします。それが生徒たちの望みを最大限尊重しながら、生徒の組み合わせを可能な限り記録するというものだとすると、ルールエンジンは恐らく非常に遅いです。一方、似たような人口統計データに対して、クエリの組み合わせを繰り返し行う必要のある保険システムでは、協調動作を行うためのカスタムコードを一切必要とすることなく、条件の共有とパターンマッチングがソリューション全体の複雑さを劇的に減らします。
ルールはどのように書くのか?
これは、ルールエンジン毎の差異が大きい分野です。複数の既存の言語をフルに、もしくは断片的にサポートしているものもあれば、プロプライエタリな言語を提供しているもの、ビジュアルな設計システムを提供しているものもあります。
一部のエンジンは、ルールの作成や管理、ルールの開発環境を含めて広範囲にわたりツールサポートを行っています。その他のものはIDEからテキストエディタ、スプレッドシートなど、既存のツールとの統合に力を入れています。
バージョン2.5では、Droolsはカスタム開発されたドメイン固有言語に加えて、XMLベースのルールファイル内でのJava、Python、 Groovyによるルールのコーディングや、ルールをJavaのPOJOで書き、SpringやJSE 5.0アノテーションで紐付けるといった方法を提供しています。
間もなくリリースされるDrools 3.0では、XMLのラッパーを必要としない独自のルール言語を持ちます。この言語は新しいユーザにとってもより読みやすいので、私もこのシンタックスを使用します。
先に述べた車両保険のルールは、Drools 3.0のDRLでは以下のようになります。
package com.insurance.auto
import com.insurance.auto.Driver
import com.insurance.auto.Car
import com.insurance.auto.Policy
import com.insurance.auto.Style
import com.insurance.auto.Color
import com.insurance.auto.Percentage;
rule "High Risk"
when
Driver( male=true, age >= 16, age <= 25 )
Car ( style == Style.SPORT, color==Color.RED )
$p: Policy ()
then
$p.increasePremium( new Percentage( 20 ) );
Drools 2.5で利用できるツールは非常に限られたものでしたが、Drools 3.0ではドメイン固有言語拡張と自動補完機能を含んだEclipseプラグインを持ち、ルールの作成やデバッグもサポートします。このように、これからはDroolsユーザ向けのツールサポートも増えていくことでしょう。
ルールエンジンは何に向いているか?
ルールエンジンには様々なものがあります。それぞれに固有の強みを持っており、あるルールエンジンを使うことで得られる利益が、あらゆるエンジンで同じように得られるわけではありません。しかし、ほとんどのルールエンジンに当てはまる一般的な性質もいくつか存在します。
第一に、かつ最も重要なことは、ルールエンジンは問題に対する異なる捉え方であるということです - 「より良い」、もしくは「より悪い」と言ったものではなく、単に異なっているのです。基本的には、汎用目的のプログラミング言語とルールエンジンは同じ類の問題を解決するために使われうるものです。しかし、ルールエンジンを使用してより簡単に解決できる問題もあれば、汎用目的のプログラミング言語が適している問題もあります。
ルールエンジンは、「どのルールがどういった順番で実行されるか」を決定するためのマッチングアルゴリズムを提供します。ルールの記述者は、必要に応じてフィルタやワークフロー、衝突解決などの制御を行うこともできます。ルールエンジンを使うと、事象をルールによって捉えなおすことができます。あるルールの結果がファクトベースに影響を与えることを許したり、必要に応じて他のルールを実行したりキャンセルしたり、といった具合です。
ルールエンジンのこうした性質により、ファクトとルールは単純でもパワフルな結果が得られるといった、巧妙な相互作用を作り出します。こうした特質は、しばしば汎用目的のプログラミング言語を超える価値を提供します。
最後に、イントロダクションで我々が論じたように、あるパターンを実践するためにルールエンジンを使用する人もいます。ビジネスロジックの外部化、迅速な変更のサポート、ビジネスユーザの力になること、などです。こうしたアプローチは、以降の文章でさらに詳しく論じます。
ルールエンジンは何に向いていないか?
あらゆる問題に対してルールエンジンを適用することも、頑張れば不可能ではないでしょうが、ルールエンジンは単に道具箱の中の道具の一つでしかありません。ルールエンジンは制約に基づくプログラミングや解法、人工知能、ワークフローエンジン、デシジョンテーブル、汎用目的のプログラミング言語を置き換えるものではありません。あくまで、他のツールを補うものでしかありません。
例: 車両保険の見積もり
ルールエンジンを使用したアプリケーションは、ルールエンジン自身によって変わってきます。ですが、特定の例を挙げることを考えると、それはしばしば便利です。車両保険の見積もりを行うシステムを想像してください。それは、顧客情報、顧客の運転経歴、現在加入している保険、車などの正確な情報や、顧客自身が受け取る見積もり自体をいくつも審査する必要があります。その後、システムは保険の見積りを用意してユーザに表示し、受諾されたり、受諾されなかったり、もしくは後日まで引き延ばされたりします。
こうしたシステムの大部分は、汎用目的のプログラミング言語で比較的素直に開発できますが、顧客の情報から見積もりの準備を行うプロセスに関しては複雑です。多くのデータ変更、ビジネスルールの変更、規制の変更などを扱うため、通常の開発プロセスでは困難です。多くの場合、これはルールエンジンの方が優れている環境の類で、見積もりを準備するプロセスをルールエンジンに任せることが可能です。
ルールエンジンを用いたアーキテクチャを構築する
新規もしくは既存のシステムにおけるアーキテクチャと、ルールエンジンを統合するための最適な方法は、個々のルールエンジンが持つ仕様によって大きく異なります。
サーバ型と埋め込み型
いくつかのルールエンジンはサーバを必要、もしくはあった方が良く、リモートからの呼び出しを行うことができます。これらは、EJBやCORBA、SOAと言った分散コンピューティングを使用した環境とうまく適合します。その他の物は完全に組み込み型で、ドメインオブジェクトとともに直接動作し、それらを直接変更します。このアプローチは、ルールと周辺ソフトウェアの間に信頼性の境界が必要ない場合にうまく適合します。さもなければ汚染を防ぐレイヤが必要になるでしょう。
ルールのパフォーマンス
分散コンピューティングを使っていない場合は概して、組み込み型ルールエンジンの方がパフォーマンスが良好です。サーバ指向のルールエンジンはサービス指向の環境内でうまく動作し、ルールエンジン自身がスケーラビリティを提供します。
ルールエンジンは、ビジネスルールを選択して協調動作させるための一般的な方法であり、完全なカスタムソリューションのような速度を提供するものではありません。しかしうまく構築されたルールソリューションは、アプリケーション全体に対して大きなパフォーマンスの劣化をもたらすものではありません。
ルールソリューションのパフォーマンスは、ルールを起動するエンジンよりも、ルール自体の構成とコードに依存しがちです。しかし個々のルールエンジンは、パフォーマンスの観点から見たベストプラクティスをそれぞれ持っているものです。それは、パターンマッチングアルゴリズム - ルールエンジンが入力を解釈してルールをスケジューリングしている間、繰り返し実行される- 全般に対して共通なものではないため、これらへの依存関係を可能な限り薄くしておくことが重要です。たとえばDroolsでは、スケジューリング処理の最初の方では条件が繰り返し実行されます。そのため、処理が高価かつ頻繁に評価されるような条件があると、パフォーマンス全体にかなりの影響を及ぼします。使用しているルールエンジン上でルールソリューションの開発に慣れるまでは、パフォーマンスに対して常に注意を払っておくべきでしょう。
ルールの管理
ルールそれ自体は、中央リポジトリで管理されユニークなIDを通して参照されるか、ファイルシステムやデータベースから直接ロードされます。ファイルシステムからルールを読み込む場合は、ルールをコードの一部としてソース管理システム内で管理したり、外部のツールセットなしでルールを変更する、と言ったことを簡単に実現できます。一方リポジトリによるルール管理では、ルールの作成/管理/デプロイを行うツールのおかげで、ソース管理システムやビルドスクリプトを理解したり、ローカルでファイルのバージョンを管理する必要がありません。そのため、開発者ではない人間がルールの作成に参加するのを容易にします。
Droolsは組み込み型で、ドメインオブジェクトと直接やり取りします。ルールは様々な方法でロードできますが、ファイルシステムアプローチが最も一般的です。
サンプルアーキテクチャ
車両保険の見積もりを作成するためのルールエンジンソリューションのアーキテクチャは、最終的に以下のような感じになるでしょう。
見積もりルールをWARの外側に置いておくことで、サーバを使用しているアプリケーションとは別のライフサイクル上で、それらをデプロイ、再デプロイすることが可能になります。ルールを軽量に保ち、実装から切り離しておくことで、ルールが必要とするあらゆる情報をルール内で直接読み込むのではなく、外から渡すようにします。
典型的な場合ルールは、実行するルールの'集合'を抽出してから呼び出され、ルールエンジン内で一連のファクトをアサートします。これらファクトは、実行される条件と結果のうち、どれがどのルールパラメータに紐付き、依存するかを表すオブジェクトです。一つ以上のアサーションが一度でも作成されると、ルールエンジンが呼び出されます。ルールの'集合'が選択されると、ルールエンジンはどのオブジェクトに対してどのルールをどんな順番で実行するかを選択します。ルールは、既存のオブジェクト集合を直接変更します。その変更によって集合の一部を削除したり、新しいオブジェクトを追加したりすることもあります。
このアプローチはルールエンジンにとって典型的なものですが、渡されたドメインオブジェクトに応じて一つ以上のアクションを実行するという点で、 Commandデザインパターンと少し似ています。Commandパターンと異なるのは、基盤となるAPIが、 (その上で動く) ルールが実行するオブジェクトやルールの結果について知る必要がないよう設計されていることです。あるルール集合を使用するためには、呼び出し元は必要とされるインプットを理解しておかねばならず、その暗黙的な作用についても少しは理解している必要があるかもしれません。
ルールエンジンがDroolsの場合、見積もりの作成は以下のようなシーケンスになるでしょう。
この場合、ルール (ロードされたRuleBaseオブジェクトとして表現されています) はWorkingMemoryオブジェクトとして表現されているステートフルなコンテキストへと取り出されます。現在の状態はワーキングメモリー内でアサートされ、ルールが起動され、何らかの見積もり結果が戻されます。これと同じパターンは、単一の見積もりを算出するシステム (保険業者の場合) でも、複数の見積もりを算出するシステム (仲介業者の場合) でも使うことができます。
ルールエンジンをサービスファサードの裏側に置くことで、「見積もりの作成のためにルールエンジンを採用した」という実装の詳細がカプセル化されます。ユーザインターフェースやサービス、データアクセスといった既存の部分は変更する必要がありません。データを収集するアプリケーションと、市場に敏感なルール - 見積もりサービスでは何度も更新され、テストされ、再デプロイされる部分 - の間で関心事の分離を行っています。これにより、(同じシステムを) これまでとは異なる新しい市場へも簡単に投入できるかもしれません。単一の見積もりを算出するサービスでも、ルールの一部を、別の地域や保険業者などに任せることができます。
ここに示した単純な例は、ルールエンジンが保険業界のコンテキスト内でどのように使われうるかを示したものです。しかし、他にもルールエンジンが適用できる場面は数多くあります。あなたのアプリケーションにルールエンジンを統合する方法は他にもたくさんあります。もっとも重要なのは、この実際に動作する例を用いて、ルールエンジンの使用におけるパターンとアンチパターンに関したさらなる議論を行うことができると言うことです。
ルールエンジンのパターンとアンチパターン
ルールエンジンを適用するために、取り得る方法は数多くあります。こうした方法はそれぞれトレードオフを持ち、あなた自身がルールエンジンを使用する際役に立つ情報をいくつか与えてくれます。ほとんどの場合、あなた自身の環境でこれらのトレードオフを検証する最も優れた方法は、ルールエンジンを実際に使ってみることです。
ビジネスロジックの外部化
アプリケーション自身からビジネスルールを外部化する手段として、多くのビジネスがルールエンジンを選択しています。これにより、アプリケーション自身とは異なるライフサイクルでルールが開発/テスト/デプロイされることを可能にします。これはしばしば、より深いビジネスのニーズをサポートします。たとえば迅速な変更や、次のビジネスを導出することなどです。ルールエンジンを使うと、アプリケーションの核となるビジネスルールをアプリケーションのリコンパイルや再配備なしに変更でき、関心事の分離を確立します。
私たちの保険システムの例では、データ収集や見積もりの表示などの静的な機能はあるライフサイクルによって開発され、特定のルール - 保険見積りの作成を行い、規制や市場、保険の支払額統計などによって変化する - は、全く異なるライフサイクル上で開発を行うことができます。
ルールエンジンは、ビジネスルールを外部化するための実現可能な手段ではありますが、それがもたらす便益はまだ探究の余地があります。つまり、他の方法でも同じような利益を得ることは可能ですし、このアプローチがもたらす便益を他のアプローチと比較して測ることが重要だということです。
もしあなたの関心事が、アプリケーションから切り離した形でビジネスルールのデプロイを行うというだけであれば、これを達成するためだけにルールエンジンを使用するのは、もっとも簡単な方法だとは言えません。ビジネスルールの実装は、(ルールエンジンを使わなくとも) 実行時にロードしたりリロードしたりすることができます。もしあなたの関心事が、明示的なリコンパイルのサイクルを避けることにあるのだとすれば、スクリプト言語の使用や、Janinoなどの動的リコンパイル技術を考慮してみてください。
こうした選択肢の多くは、ルールエンジンと比べると時間やエネルギー、ライセンスコストの節約になります。ですがルールエンジンは、その他の領域でも多くの利点を提供しています。
変更の迅速性
ビジネスルールは、環境によっては頻繁に、絶え間なく変わります。あるチームは、ルールエンジンを使ってこうした種類の変更をサポートしようとしています。
例えば前の節で述べたように、アプリケーションコードからビジネスルールを外部化し、これらのルールを別に変更し、デプロイ、またはホットデプロイさせることができます。加えていくつかのルールエンジンは、ビジュアルな設計や動的言語をサポートしており、迅速な開発アプローチをサポートしています。
保険の規制や新しい統計は、短い期間で適用されなければならない変更を引き起こし得ます。従って、ルールの生成を行うロジックが、この種の変更を素早く行うことをサポートしているかどうかを知っておくことは重要です。
結局、ルールエンジンは変更をサポートする魔法の弾丸ではなく、助けになるものです。しかし、ルールエンジンを使おうと使うまいと、これらと同様のアプローチの多くを得ることはできますが、こうしたアプローチを自分で実装するのは、ルールエンジンに付属するツールを使用するほどにコスト効果が良くありません。
ビジネスロジックの外部化と同様、変更の迅速性だけがあなたの関心事ならば、ラピッドデベロップメントツールやスクリプト言語を考慮するとよいでしょう。これらは作業、時間、金の投資がルールエンジンよりも少なくて済みます。
使われる技術に関係なく、素早い変更が必要な環境でも (そういう環境なら特に)、テストが最も重要な関心事である、と言うことを強調しておくのは重要です。テストなしに、動作中のアプリケーション内で複雑なビジネスルールを変更するのは、災害を引き起こすためのレシピのようなものです。
ビジネスユーザがビジネスルールを開発する
ビジネスルールは通常、ビジネスに携わっている人によって定義されます。しかしほとんどのビジネスでは、それらは開発者によって実装されています。いくつかのチームは、ビジネスユーザが開発者のサポートなしに (もしくは最低でもサポートを減らして) ルールを開発できるよう、ルールエンジンを変えようとしています。それが成功した暁には、ビジネスユーザ自身がビジネスソリューションのコントロールを行い、システムに継続的に関与し、システムに何ができて何ができないかを理解し、根本的に新しい機能をユーザが求めたときのみ開発者に問い合わせる、と言ったことを促進するでしょう
こうした事柄をルールエンジンが改善する、と言うのはしばしば正しいのですが、並の複雑さを持つルールソリューションでさえも、そのほとんどが開発者の関与を必要とする、と言うのもまた真です。ツールサポートが極めて使いやすくなったとしても、境界値のケースや極端な条件式など、多くのビジネスユーザにとって開発する必要がない、技術的な分析の考え方や困難が存在します。
こうした場合において最も成功するソリューションは、開発者とビジネスユーザの間の協力関係を促進することです。開発者が境界を引き、大まかな構成については準備し、必要とされる技術的な複雑さを取り扱います。ビジネスユーザは、よりコントロールされた環境下で、個々のビジネスルールの仕様をカスタマイズすることができるのです。
車両保険の仲介業者にとっては、以下のような目的のために、開発者を雇用すると良いでしょう。すなわち、洗練されたデシジョンテーブル - 特定の市場向けに構成することが可能な、もしくはビジネスユーザによって行われる、経済的なパラメータ変更をサポートする - を前もって作成するためです。
我々が携わった、さらに最近のプロジェクトのうちのいくつかで、Droolsの"デシジョンテーブル"を使って非常に効果を上げることができました。ビジネス上の決定をルールに任せた形のビジネスドメインとアプリケーションを、Java開発者が作成、拡張しました。そして一連のデシジョンテーブルの構造を作成するために、ソリューションの境界で区切った形式の、複数のExcelワークブックを使用しました。するとビジネスを分析する側のチームメンバーは、ビジネスのゴールを満たすためにこれらのテーブルをパラメータ化することでルールを作成することができました。このようなものです。
RuleTable agerRules (Policy policy, Driver driver) |
||
CONDITION | CONDITION | CONDITION |
driver.getAge() >$param | driver.getAge() <=$param | policy.setPremium( $param) |
Age Greater Than | Age Less Than or Equal | Premium |
16 | 25 | 150% |
25 | 40 | 100% |
40 | 60 | 80% |
60 | 200 | 125% |
これはかなり成功しました。この仕事ではいくつかのイテレーションを経て、開発者のサポートを受けながらも、ビジネスユーザはさらに多くの責任を負ってデシジョンテーブルの構造を作成しようとしています。
こうした習慣を実現するためにルールエンジンを検討する場合は、都合のよいビジネスユーザを見分けることが重要です。すなわち、ビジネスルールを作成/修正したいと望んでおり、それらを試し、できればビジネスユーザにとって都合のよいシナリオだけではなく、いくつかの現実的なシナリオを開発するためにツールを使用してくれるようなビジネスユーザです。もしくは、ビジネスユーザが使いたがらないツールに対して全力を傾ける前に、製品に対していくつかの検討を行うための、適度に長い評価期間を考慮してください。
フロー制御の放棄
いつ、どのルールを実行するかをルールエンジンの決定に任せることは、ルールエンジンのフルパワーを引き出すために重要なステップです。ビジネスロジックの実行を制御することに慣れた開発者たちにとっては、この移行は厄介なものに思えます。しかし一度こうした方法に慣れて来ると、かなりのパワーを提供してくれるものだということがきっとわかるでしょう。
多くのルールエンジンは、実行に影響を及ぼすルールの選択と順番づけを行います。Droolsは、コンフリクトを解決するストラテジや、それらを制御するためのsalienceやagenda-groupと言ったルールの属性を数多く提供しています。こうした種類のオプションにより、フロー制御の全体をルールへと分離することができます。しかし、ビジネスロジックの要件を完全に満たすため、もしくはルールエンジンが採用しているアプローチを調整するために、必要なコントロールは何でも使うよう努力しなくてはなりません。
手続き型のコードとしてのルール
もしあなたが手続き型のプログラミングをかなり多く行ってきたのであれば、ルール指向の開発モデルに切り替えることは少しハードルが高いかもしれません。あなたは慣れ親しんできた手続き型の開発パターンにすぐさま陥り、以下のような質問をするでしょう。
- 一つのルールだけを実行するにはどうしたらいいのか?
- 実行したいルールを順番どおりにリストできるか?
重要なのは、こうしたことを行うこととは可能ですが、常に望ましいわけではないということです。たとえばDroolsでは、アクティベーションフィルタを使用すればどのルールが実行されるかを制御することができますし、コンフリクト解決を使用すれば順番を制御することもできます。こうした方法をとるのは我慢してください。これらが必要な場合もありますが、これらを使うとあまりにも簡単に、古い手続き型のパラダイムに陥ってしまいます。
クロス乗積
ルールエンジンに与えるファクトを組み立てる際には、発生する組み合わせや、少数のファクトとルールの組み合わせでも、結果としてその組み合わせ数がすぐに驚くほど膨大になってしまうと言うことをすぐに忘れてしまいます。
例えば、2つの車種と一人の顧客を参照する保険のルールがあるとします。(そのルールが参照する) ナレッジベースが5,000の車種と5,000人の顧客から構成されていたなら、エンジンは2,500万もの車種の組み合わせを5,000人の顧客それぞれに考慮しなければならず、トータルの組み合わせは1,250億にもなります。
もしルールエンジンに比較させるものの規模を考慮し忘れると、あなたはこうしたルールを書いてしまい、ソリューションが期待したようなパフォーマンスを発揮しないのを目の当たりにすることになるでしょう。
従ってルールソリューションの開発を行う際には、規模に対するセンスを持つことが重要です。これは、ルールに渡し、導出される情報の量を制限したり、定期的なパフォーマンステストと負荷テストを確立し、継続的にメトリックスを評価することによって実現することが可能です。
組み合わせと順列
一つのルールが、特定のタイプのファクトを一つ以上とるとき、ルールエンジンはこれらのファクトに対する全ての順列と組み合わせを提供しようとします。つまり、もしナレッジベースが3人のドライバー (Adam, Beth, Chris) を持ち、ルールがそれらのうち2人を必要としたとすると、ルールエンジンは以下のような組み合わせにより、ルールを9回実行するでしょう (Adam, Adam), (Adam, Beth), (Adam, Chris), (Beth, Adam), (Beth, Beth), (Beth, Chris), (Chris, Adam), (Chris, Beth), (Chris, Chris)。
こうした振舞いを必要とするファクトもありますが、その他の場合では、一意となる組み合わせや重複のない組み合わせが望まれることでしょう。これは通常、ルールの条件によって行うことができます。
ルールにおいて項目の重複を避けるには、一つ目の項目が二つ目と同一でない、と言う条件をつけ加えてください。以下のタプルが除去され (Adam, Adam), (Beth, Beth), (Chris, Chris) 、6つの組み合わせが残ります (Adam, Beth), (Adam, Chris), (Beth, Adam), (Beth, Chris), (Chris, Adam), (Chris, Beth)。
一意となる組み合わせを得るには、オブジェクトの順序付けを使用することができます。たとえば、二番目の人名は最初の人名よりもアルファベット順で後になる必要がある、とすると以下のタプルを除去でき、 (Beth, Adam), (Chris, Adam), (Chris, Beth)、次の3つが残ります (Adam, Beth), (Adam, Chris), (Beth, Chris)。Javaではオブジェクトのハッシュコードを用いて、こうしたことを実現できます。
いくつかのルールエンジンは、組み合わせと順列の数を減らすためのこうした機能を持っています。従って、あなたが使用するルールエンジンが持つこうした機能や、どのような組み合わせが生成されるのかを理解することは重要です。Drools 2はこうした組み合わせの全てを提供しています。Drools 3では、少なくともRC2の段階では、アイデンティティ フィルタによって重複した項目を除去します。
再帰
ソフトウェアにおけるフロー制御のいかなる形式でも、自身の再帰呼び出しは問題となります。たとえば、ルールがファクトベースを変更してしまい、その変更によって同じルールが再度呼び出されるとすると、結果は無限ループでしょうか?
もしくは、あるルールが他のルールを呼び出し、最終的に最初のルールを再度呼び出すような、ルールのサイクルを作ってしまうこともあります。こうした種類のサイクルは、ルールを設計する際に防いでおかないと、十分あり得ることです。
解決策は存在します - いくつかのケースでは、注目に値するほど単純ですが、ルールを定義するときに、決して自分自身を呼び出さないようにするというものです。Droolsは、こうした場合に使える'no loop'属性を持っています。そうした方法を用いて、自身を呼び出さないようルールを設計する、と言うのが多くの場合最良の解決策です。
例えば、保険見積りに割引を適用するルールを使用していたとして、見積もりに指定された割引を行わない、と言うルールの条件を単純に作成します。これは、割引のルールが自信を呼び出さないと言うことを意味するだけではなく、一般的なケースを最も優先度が低いものとする、割引ルールの階層構造を構築することができ、それらは奇麗に相互作用します。
こうした類の設計上の決定は、ルールエンジンに対するルールの開発に十分時間をかけると、自然に行えるようになります。しかし、あなたが手続き型やオブジェクト指向のコードを書いてきたという経験があるなら、すぐにはっきり理解できるわけではありません。
粒度
ルールエンジンを初めて使うときには、典型的なプログラミングパラダイムに則って、ルールやそれを動かすデータの粒度を扱いたくなるものです。たとえば、
- 巨大で複雑なルールを書き、フロー制御をルールエンジンではなくルールに行わせる
- きめの細かいファクトを使い、それらの中から適切なファクトを抽出することをルールエンジンに行わせるのではなく、巨大なオブジェクトグラフをルールエンジンに与え、ルール内でオブジェクトグラフを操作する
こうしたアプローチにより、既存のアプリケーション内にルールエンジンを素早く統合し、新しいテクノロジーによってもたらされるリスクが露見するのをコントロールできます。しかしこうしたアプローチは、ルールソリューションが持つパワーを制限することにもなってしまいます。ルールエンジンの実装がもたらす新たな価値を享受するには、ルールエンジンにより自由を与えるのが最良の方法です。ルールエンジンに対するあなたのコントロールを緩和するためには、
- 小さくてきめが細かく、単純なルールを作成する
- 小さくてきめが細かく、単純なファクトからルールを作成する
こうしたルールは、有効/無効をより簡単に切り替えることができます。簡単に組み合わせることができます。コンフリクトの解決により、ルールエンジン内で順序替えを行うことができます。そして、開発者以外にも簡単に書くことができます。しかし、それらがルールの集合として一斉に適用されると、その結果は驚くほど複雑なものとなります。
DroolsのJava/XMLルールを使った以下のような例を考えてみてください。
package com.insurance.auto
import com.insurance.auto.Driver
import com.insurance.auto.Policy
import com.insurance.auto.Percentage
rule "Age Rule"
when
$p: Policy ( declined == false )
then
Driver driver = $p.getPrimaryDriver();
if( driver.getAge() < 25 || driver.getAge() > 60 )
$p.increasePremium( new Percentage( 50 ) );
else if( driver.getAge() <= 60 && driver.getAge() < 40 )
$p.discountPremium( new Percentage( 20 ) );
else
このケースでは、通常は一つ以上のルールで構成するのが適切な、条件分岐のロジックを含んでいます。同様に、PolicyからDriverを取り出すと言うドメインオブジェクトのナビゲーションに頼っているため、Policyを必要とせず、Driverだけを必要とするようなルールを記述するのが難しくなっています。
このルールは、以下のような形式の、いくつかのルールに書きなおすことができます。
package com.insurance.auto
import com.insurance.auto.Driver
import com.insurance.auto.Policy
import com.insurance.auto.Percentage
rule "Young Premium"
when
$d: Driver ( age < 25 )
$p: Policy ( declined == false, $pd:primaryDriver -> ($pd==$d) )
then
$p.increasePremium( new Percentage( 50 ) );
end
こうしたルールはよりシンプルで、書くのも理解するのも容易で、疎結合のマナーに従ってファクトを使用しています。
まとめ
ルールエンジンは、ビジネスロジックの外部化を行い、ビジネスユーザがルールを記述出来るようにするための便利なツールです。特定分野における問題を効果的な方法で解決できます。他のアプローチと比較するとトレードオフが存在するので、その使用を決断するには系統的なアプローチが重要です。
- あなたが解決しようとしている問題と、あなたが成し遂げようとしていることを理解する
- 汎用もしくは特定のルールエンジンが、それらのゴールにたどり着く助けとなるかを調査する
一般的には、ビジネスルールの外部化やビジネスルールの変更に対する迅速な変化のサポート、ビジネスユーザによるビジネスルールの変更を促進したい、などのニーズがある場合は、ビジネスルールソリューションを考慮してもよいでしょう。もしあなたが、フロー制御の放棄によって新しいパラダイムを受け入れ、きめの細かいルールとオブジェクトを使用し、クロス乗積を回避し、ルールアプローチが生成しうる組み合わせと再帰について理解するなら、ルールエンジンを最大限活用することができるでしょう。
いくら考えても、あなたのニーズをルールエンジンが満たしてくれるかどうかわからない場合は、Droolsのようなフリーで利用可能なルールエンジンを使って、低コストでコンセプトを実証するための開発を行い、ルールエンジンがあなたの問題領域にとって適切かどうかを、自分自身で評価してみてください。
もし、ソリューションの一部にルールエンジンを採用すると決めたなら、ルールエンジンは設計と開発において多少の新しいスキルを必要とすること、あなたのチームがこうしたスキルを身につけるまでは開発コストや間違いがいくらか増えるだろう、と言うことを受け入れてください。
サイドバー: その他のルールエンジン
この記事ではDroolsをサンプルのルールエンジンとして挙げましたが、その他にもオープンソース、商用問わず存在します。それらの完全なリストとその比較は、この記事の範囲を超えていますが、一般的なものを他にもいくつか挙げておきます。
これらのルールエンジンの多く、特に商用製品では、この記事で詳しく述べたよりもより多くの機能、サービス、モジュール、コンポーネント、そしてツールを提供しています。ルールエンジンを選ぶ際、以下の事柄のうちいくつかを考慮することになるでしょう。
- ビジネスユーザと開発者の双方、そして両者の協力関係にとって力となるツール
- ルールを記述するツール: Webベース、デスクトップベース、開発者用 (例えばEclipse) とビジネスユーザ用 (例えばExcel) のツールに統合されたもの
- ルールのテストを行うツール: テスト、シミュレーション、エラーチェック、デバッグ、依存性のチェック
- ルールのアルゴリズムとパフォーマンス
- ルールの編集、レビュー、承認というワークフローを管理するための、バージョン機構付きリポジトリ
- 既存のソフトウェアやビルドアーキテクチャとの統合
- オペレーション支援: モニタリングや監査
- トレーニングとサポート
著者について
Geoffrey Wiseman はソフトウェア開発のジェネラリスト、ライター、たまにコンサルタントなど、全方位に対して熱心な人物です。ここ数年は金融サービス産業に重点を置き、為替取引ソリューション向けの顧客価値を管理する製品に従事しています。楽しみもしくは仕事のための開発、ソフトウェア仕様の読み書き、flickrへの投稿、ビデオゲームのプレイ、オープンソースコミュニティへの寄与、インターネット中の興味深いソフトウェアコンテンツを全て読もうという試み、そして彼自身のあらゆる楽しみの間に、彼はこの記事を書く時間を見つけてくれました。
原文はこちらです:http://www.infoq.com/articles/Rule-Engines
(このArticleは2006年6月19日に原文が掲載されました)