BRMSで実験してみました-宣言的プログラミングのすすめ (2)-

Droolsを使って、ルールベース(BRMS)の動きを見るためにちょっと実験してみました。Droolsで、Projectをつくるとサンプルのプログラムをつくってくれますが、それをすこしばかり修正しての実験です。
(ルールベースの動きの基本は、プロダクションシステムとはとか、当ブログのルールベースプログラミングのカテゴリなどを参照ください)

まずは、ルール処理の対象となるファクトを準備します。

SampleFact.java

package com.sample;

public class SampleFact {
    private int num;
    private String name;

    public SampleFact() {
        super();
    }

    public SampleFact(int num, String name) {
        super();
        this.num = num;
        this.name = name;
    }

    /**
     * @return the num
     */
    public int getNum() {
        return num;
    }

    /**
     * @param num the num to set
     */
    public void setNum(int num) {
        this.num = num;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "SampleFact [num=" + num 
                   + ", name=" + name + "]";
    }

}

そして、mainプログラム

DroolsTest.java

package com.sample;

import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

/**
 * This is a sample class to launch a rule.
 */
public class DroolsTest {
  public static final void main(String[] args) {
    try {
      // load up the knowledge base
      KieServices ks
       = KieServices.Factory.get();
      KieContainer kContainer
       = ks.getKieClasspathContainer();
      KieSession kSession
       = kContainer.newKieSession("ksession-rules");

      // go !
      SampleFact sf1 = new SampleFact(1, "A");
      SampleFact sf2 = new SampleFact(2, "B");
      SampleFact sf3 = new SampleFact(3, "C");
      SampleFact sf4 = new SampleFact(4, "D");
      SampleFact sf5 = new SampleFact(5, "E");
      kSession.insert(sf1);
      kSession.insert(sf2);
      kSession.insert(sf3);
      kSession.insert(sf4);
      kSession.insert(sf5);
      kSession.fireAllRules();
    } catch (Throwable t) {
      t.printStackTrace();
    }
}

上のように5つのファクトをワーキングメモリに追加して、以下のルール

Sample.drl

package com.sample

rule "サンプルルール1"
when
    SampleFact($num: num, $name: name)
then
    System.out.println("num=" + $num
         + ", name=" + $name);
end

を実行しました。
コンソールには、どのように表示されるでしょうか。

まあ、これはワーキングメモリに5つのファクトがあるので、単純に
条件節(when部分)のSampleFactにマッチして、5回ルールのサイクルが回ります。

num=5, name=E
num=4, name=D
num=3, name=C
num=2, name=B
num=1, name=A

つまり条件部にマッチしたファクトが
SampleFact(1, “A”);
SampleFact(2, “B”);
SampleFact(3, “C”);
SampleFact(4, “D”);
SampleFact(5, “E”);
の5つだったということ。

では、上記のサンプルルール1のかわりに次のルールがあった場合は
どうなるでしょうか

Sample.drl(改)

package com.sample

rule "サンプルルール1a"
when
    SampleFact($num: num > 3, $name: name)
then
    System.out.println("num=" + $num
         + ", name=" + $name);
end

このときは条件節に num > 3 という条件が加わったので
num=5, name=E
num=4, name=D
の2つのみが表示されます。

すなわち条件部にマッチしたファクトが
SampleFact(4, “D”);
SampleFact(5, “E”);
の2つということ。

さらに次はどうでしょう。

Sample.drl(改々)

package com.sample

rule "サンプルルール2"
when
    SampleFact($num: num, $name: name)
    SampleFact($num1: num, $name1: name)
then
    System.out.println("num=" + $num
         + ", name=" + $name
         + ", num1=" + $num1
         + ", name1=" + $name1);
end

今度は、条件節のSampleFactのパターンが2つになりました。結果は、
num=1, name=A, num1=1, name1=A
num=1, name=A, num1=2, name1=B
num=1, name=A, num1=3, name1=C
  ・・・(中略)・・・
num=5, name=E, num1=4, name1=D
num=5, name=E, num1=5, name1=E

以上、25行表示されました。

これは、条件節の最初のパターン
SampleFact($num: num, $name: name)
に5つのファクトがマッチして、さらに2番目のパターンにも
SampleFact($num1: num, $name1: name)
5つのファクトがマッチして結局その組合せとして5×5=25(通り)の
組合せが表示されているということを表しています。つまり

条件部にマッチしたファクトの組を
[<最初のパターンにマッチしたファクト>,<2番目のパターンにマッチしたファクト>]
の形であらわすとすると、

[SampleFact(1, “A”), SampleFact(1, “A”)]
[SampleFact(1, “A”), SampleFact(2, “B”)]
[SampleFact(1, “A”), SampleFact(3, “C”)]
  ・・・(中略)・・・
[SampleFact(5, “E”), SampleFact(4, “D”)]
[SampleFact(5, “E”), SampleFact(5, “E”)]

の25個のファクトの組が条件部にマッチしたことになります。

では、こんなルールであったらどうでしょうか。

Sample.drl(もひとつ改)

package com.sample

rule "サンプルルール2a"
when
    SampleFact($num: num, $name: name)
    SampleFact($num1: num > $num, $name1: name)
then
    System.out.println("num=" + $num
         + ", name=" + $name
         + ", num1=" + $num1
         + ", name1=" + $name1);
end

(この項続く)

CEP(Complex Event Processing)の世界市場 2017-2021

またまたずいぶんと間があいた投稿となってしまいましたが、ちょっと前にCEP(複合イベント処理)でググった(というかビンした)ら、市場調査レポートの話が出ていました。CEPについては、このブログでもときどき触れていますが、調査レポートによれば複合イベント処理の世界における市場は2017から2021年にかけて、年平均(CAGR)にして18.42%の成長が見込まれるとのこと。

目次だけ見ると、セキュリティ目的での使用が増えてきたりしている一方、当然のことながらIoTがらみでも増えてくるような予感。どこかでちゃんとCEPもまとめておきたいところなのですが…。

たとえば、
複合イベント処理の世界市場:2017~2021年
https://www.gii.co.jp/report/infi551749-global-complex-event-processing-market.html
とか…

if-then文とif-thenルール -宣言的プログラミングのすすめ (1)-

この前の更新からずいぶんと間があいてしまいましたが、最近、ビジネスルールというか、「ルール」というものの認識についてどうも気になることがあったので、考え方としては前回と重なる部分も多いかと思いましたが、再び記事を書いてみました。またこれに限らず、ぼちぼちと記事を書いていきたいと思っております。

さて、ちょっと唐突ですがプログラムにおけるif-then文と、一般的なルールとしてのif-thenの違いはどこにあるでしょうか。
(なお、ここで言うルールとしてのif-thenは、実際にBRMSに実装されるif-thenルールではなく、それ以前のまずは仕様(文書)としてまとめられた(ビジネス)ルールとします)

これら if-then文 と if-then ルール、同じ if-then ではあるので、それほど変わらないように感じられるかもしれません。私自身もふだんは明確な使い分けをせずあいまいな書き方をしていたりもしますが、実は大きな違いがあります。

まず、プログラムにおけるif-then文。これは、プログラムの上から下に流れる処理の流れが前提にあって、何もしなければif-thenは1回しか通りません。もう一度通すためには繰り返しを書く必要があります。つまりプログラムのif-then文は処理の流れを意識しないと成り立たず、処理の流れはすべて記載しなければなりません。すなわちプログラムのif-then文は、いわゆる手続き的 (procedural) にしか解釈されません。

一方、ルールにおけるif-thenというものは、どうでしょう。一般にルール(規則)というものは、条件 (if部) が合うのであれば常に適用されるもので、そこに処理の流れというものは存在しません。仮にいくつかif-thenルールがあったとしましょう。原則それぞれのルールは独立してそれぞれ条件が合うかどうかのみで、適用される/されないが判断されます。そこにどの順に処理するかという処理の流れはありません。

たとえば

 if 赤信号 then 止まる

というルールですが、これを手続き的に解釈して処理の手順を考え、先ほど赤信号を処理したので、次の赤信号は処理できず結果的に無視してしまうということはないでしょう。どんな状況でも、条件さえ合えば常に適用されるというのがルールです。

無理やり手続き的に書こうとすると、「if 赤信号 then 止まる」という if-then文 をループで囲って、1回赤信号を処理しても、次の赤信号も処理できるように待っているといった書き方になります。

また、個々のデータが○か×の値をとる配列があり、

 if × then エラー出力

などといったルールでも同じです。ルールそのものは上記で書けても、手続きを明示しないといけないので、if-then文 を配列全体にわたってループする・・・という追加の記述が必要になります。

まとめますと、仕様としての if-then ルールを手続き的に書こうとすると、ループなどの処理手順も明示する必要があり、それが複雑になると本来の仕様が処理手順に埋もれてしまうということになります。たとえば前回の記事などのようにデータに親子関係があり、複数の子の間での制約など if-then ルールとして記述すれば比較的わかりやすいけれども、手続き的に書こうとすると面倒というケースです。従来の手続き的なプログラミングの問題点の一つはそんなところにもあるのではないでしょうか。

    ***

さて、みなさまは「宣言的」(あるいは宣言型)プログラミングという言葉をお聞きになったことがありますか?

Wikipediaの宣言型プログラミングという項には、

「宣言型プログラミング(英: Declarative programming)は、プログラミングパラダイムの名称だが、2種類の意味がある。第一の意味は、処理方法ではなく対象の性質などを宣言することでプログラミングするパラダイムを意味する。第2の意味は、純粋関数型プログラミング、論理プログラミング、制約プログラミングの総称である。」

とあります。

上にあげた第2の意味は、現在一般的に認知されているプログラミングパラダイムの中で、第1の意味での宣言型プログラミングを具現化しているものとも解釈できるかと思います。そういった視点で第2の意味に加えるとしたら、プログラミングパラダイムとして一般的に必ずしも認知されているわけではないけれども、たとえばSQLなどはあげられるでしょう。実は、前回あげた ルールベース(プロダクションシステム)のプログラミングも宣言的なプログラミングの仲間に入れていいかと思います。

宣言型プログラミングの第1の意味では、処理方法ではなく対象の性質などを宣言することでプログラミングするパラダイムを言いますが、「対象の性質」と言っても、なかなかわかりにくいのでもう少しかみ砕いた記述を見てみましょう(宣言型プログラミングの可能性と限界より)。

「宣言型プログラミングが記述するものは、問題の定義、すなわち解くべき問題の性質や、その際に満たすべき制約の記述です。」

「対象の性質」をかみ砕くと「問題の定義、すなわち解くべき問題の性質や、その際に満たすべき制約」ということになるでしょうが、これをルールベースによる宣言的プログラミングに置き換えると、if-then ルールを使って、問題の性質や制約を記述することになりましょう。このことは何を意味しているのでしょうか。

    ***

仕様としての if-then ルール、これは問題の定義-問題の性質や制約を記述-を記述したものなので、実は「宣言的プログラミング」の意味での「宣言的」であるといえます。

これを実装する場合、従来のプログラミングでは if-then ルール を手続き的に直して書くだけだったので、本来の仕様以外に処理手順なども記述する必要がありました。

手続き的

これにより本質がぼやけてしまいプログラムの可読性が落ちることもありましたが、これはすなわち本来宣言的に表しているものを手続き的に表そうとすることで仕様と実装との間でのミスマッチ(注0)が起こっていたということなのです。

もし宣言的に表されている仕様を、そのまま宣言的に表せるプログラム手法があるのであれば、それに越したことはなく、プログラムの可読性を落とすことなく実装を行うことができます。

宣言的

ここであらわれたのがルールベースのプログラミング(プロダクションシステム)でした。もともとルールベースの技術は人工知能の応用としてエキスパートシステムの構築のために生まれてきたわけですが、このルールベースにおける if-then ルールの動きというものが、条件 (if部) さえ合うのであれば常に適用されるという、まさに仕様としての if-then ルールの動きと一致したわけです(注1)。

すでに前回にも書きましたが、ルールベースのプログラミングでは、if-then 文とループを使って処理手順を記述するのではなく、if-then ルールのみの仕様を記述することでプログラミングを行います。

で、if-then ルールを書けば、あとはルールのエンジンが、いい塩梅で実行してくれるというわけ。

たとえて言えば、手続き型のプログラミングは、処理の手順を一から教えなければならない新入社員のようなものである一方、ルールベースによる宣言的プログラミングは、「これこれをやっておいて(問題の性質や制約 ! )」と伝えておけば、それなりにいいようにやってくれる、常識的な処理手順はわかっている入社1~2年目の社員といったところでしょうか。

    ***

もっとも、このルールベースでのプログラミング、他の宣言的なプログラミング手法と同様、実際に実行するという視点から言えば良いことばかりではなくて、エキスパートシステム構築ツールの時代からメモリなどのコンピュータリソースを食うデメリットがありました。

ただ、昔のスーパーコンピュータの性能が ipad2と同じくらいであるといわれるようになった今日、プログラムの開発・メンテナンスのコストに対し、コンピュータリソースのコストは相対的に飛躍的に減少しているので、それほど気にすることはないでしょう。

    ***

まあ、そうは言っても実際のところ単項目のエラーチェックなどで、if-then文とif-thenルールとの違いがあまり表面化しない場合(単純なデータ構造で1回 if-then のチェックをすればよい場合など)も多々あるので、if-thenルールを記述できるBRMSを使うまでもないということもあるでしょう。

そんなときには、ちょっと手軽で安価な、手続き的に if-then文 を処理するBRMS (たとえばOpenRulesのsequential rule engine) を使ってもよいかと思います。

ただルールを手続き的な if 文で書こうとすることは、本質的にはJava(注3)やCobolなどなど従来の手続き型プログラミングをなぞっているという域を出るものではなく、本来のルールの宣言性を生かした宣言型のプログラムにはならないということは、きちんと認識しておいた方がよいかと思います。

手続き的な処理方法のBRMSで多少複雑な処理を行おうとすると、本来のBRMSのメリットである可読性が失われてしまい、メンテナンスの視点からは、結局従来のプログラムと何ら変わらないことになってしまうでしょう(注2)。要するに限界は限界として認識した上で、向き、不向きを判断して使うことが重要かと思います。

   ***

今回は、概念的な宣言的プログラミングの説明で終わってしまいましたが、どこかで宣言的プログラミングの例をもう少し書いてみたいと思います。たとえばビジネスの例ではありませんが、ポーカーの役の判定などは、手続き的に書くとするとループを回したりする必要がありますが、ルールベースで書くと役の定義に素直に、比較的直感的に書けるので、良い例になりそうだと思っています。

(注0) ミスマッチという言葉を使ったのは、ここを書いていて、一昔前によく言われていたオブジェクト指向言語からRDBを呼び出す際に、それぞれの拠って立つモデルが違うことから実装が複雑になる「インピーダンス・ミスマッチ」という言葉が頭に浮かんできたので・・・。

(注1) ちなみに、これが、現在の一般的なBRMSの多くが、エキスパートシステム構築ツールをルーツに持っているという所以です。たとえば、IBMのOperational Decision Manager、FICOのBlaze Advisor、RedHatのJBoss Rules、Oracle Business Rulesは、いずれもReteアルゴリズムベースのエキスパートシステム構築ツールをルーツに持ちます。またSAPのルールエンジンはReteベースのようですし、ProgressのCorticonは、DeTIという独自のアルゴリズムですが、動き的にはReteと同等です。

(注2) なお、OpenRulesにはそのために制約プログラミングをベースとした Rule Solver というもうひとつのルールエンジンがあり、上記にあげたルールベース的な動きではありませんが、宣言的なプログラミングができるようになっています。

(注3) 余談ですが、最近では Java も流行の関数型のパラダイム (上にも書きましたが宣言型のパラダイムの一つ) を取り入れるようになってきています。ルールベース的な宣言とは、ちょっと違うのですが、個人的には、関数型は関数型で結構好きだったりもします。

プログラミングパラダイムとしてのルールベース

サグラダファミリアBRMSの多くが依拠しているルールを用いたプログラミング(ルールベースプログラミング)について、最近はなかなか正面からの解説が少ないように思います。これは、最近のBRMSの適用例の多くが入力チェックや、査定、不正検知など、あまりルールベースのプログラミングのプログラミング上の特徴が表れてこない例が多く、あえてルールベースプログラミングを強調することもないということだからでしょうか。

(なお、ここで言う「ルール」を用いたプログラミングとは、通常のプログラミング言語で言うところの if 文を手続き的に整理した「ルール」ではなく、いわゆるプロダクションシステムにおける「ルール」、すなわちアルゴリズム的にはRete(およびその改良版)、DeTI、(最近では)Phreakなどを採用しているルールエンジンの「ルール」、を用いたプログラミングのことになります)

とはいえ、やはり通常のプログラミング言語 -手続き型- の延長としてルールベースプログラミングを理解してしまうと、時によってルールベースプログラミングの良さを引き出せないままBRMSを使ってしまったり…ということがありそうな例を (かなり前ですが) 見聞きしたのでちょっとここで紹介したいと思います。

例題
ある入力画面で、氏名の入力欄(たとえば保険金受取人)が
10段並んでいる。入力チェックとして、違う欄に同じ氏名が
入力されていたらエラーとしたい。

これ、通常のプログラミング言語で書こうとすると、二重の for 文 を回して同じ氏名が入力されていないかチェックするということになると思います。ところが、ルールベースプログラミングですと 「違う欄に同じ氏名が入力されていたらエラー」 ということをそのまま一つのルールとして実装するだけでプログラミングが完了します。

rule "重複チェックルール"
    when
        c1 : Column( id1 : id_No , name1 : name != "");
           // 名前が空白のものは対象外にする
        c2 : Column( id2 : id_No > id1, name == name1 );
           // 欄は違う (id_Noが違う)  けれども 名前は同じ。
    then
        System.out.println("重複がありました。名前は" + name1
                             + "id=" + c1.getId_No()
                             + " と id=" + c2.getId_No()
                             + "です。");
end

なお、ここで名前欄を表す

public class Column {
    private int id_No;
    private String name;
    // getter, setter  ほか
}

というクラスを用いています。

このルールを以下のデータ

Column [id_No=1, name=野中]
Column [id_No=2, name=伊丹]
Column [id_No=3, name=伊丹]
Column [id_No=4, name=大前]
Column [id_No=5, name=嶋口]
Column [id_No=6, name=野中]
Column [id_No=7, name=]
Column [id_No=8, name=野中]
Column [id_No=9, name=]
Column [id_No=10, name=]

に適用してみると、

重複がありました。名前は野中id=1 と id=6です。
重複がありました。名前は野中id=1 と id=8です。
重複がありました。名前は伊丹id=2 と id=3です。
重複がありました。名前は野中id=6 と id=8です。

といった結果になります。

このようにルールベースのプログラミングでは、繰り返しを明示的に書くことは多くの場合必要ありません。もちろん、ルールベースでもカウンタにあたる属性あるいはFactなどを作って繰り返しを表現することは可能なのですが、概して繰り返しを書かずとも望む結果が得られる場合が多いです。なので、もし繰り返しが必要になりそうなときは本当にその繰り返しが必要かどうか自問自答してみるべきです。

この繰り返しを書く必要がないというところは、次のルールベースプログラムの実行原則のひとつに拠っています。

条件に合うものは「全部」実行する。実行するものがなくなったら終了。

ここの「全部」というところは、通常の手続き型のプログラミング言語では for や while などや、さらにたとえば iterator を使って繰り返しも含めたプログラミングすることになるでしょうが、この繰り返しは、ルールベースでは上記にあげた実行原則に見るように、すでに実行の機構として言語に含まれてしまっています。したがってルールベースプログラミングでは、「何をするか」というルールの記述のみを行えばよいことになります。

翻って、人間の職場の作業依頼/指示について考えてみましょう。たとえば、書類の束を渡して、「XXのチェックしておいて」とか「チェックして印鑑お願いします」とか…「何を」行うかのみの依頼/指示だけで、手続き的に「上から順に」チェックするとか、「全部」チェックするとかの指示はしないのが普通でしょう。

ルールベースプログラミングの基本は、「どのように」処理を実行するかの記述を省き、単刀直入に「何をするか」ということをルールで記述していくところにあります。これがまさにルールベースプログラミングが宣言的プログラミングであるといわれる所以であり、また、ルールベースのプログラムが人間の感覚に近いと言われる所以でもあります。

上の例のルールを見てみましょう。処理そのものを追ってみると、

条件部の1行目(c1にあたるところ)は、Column( というところだけを見ると、データとしてあげた10個のColumnファクトすべてがマッチする可能性がありますが、name != “” というところまで見ていくと、10個のファクトのうち id_No=7,9 のファクトは対象外となります。残りの8個のファクトが c1 にマッチするということになります。
次に2行目(c2)。id_Noがc1のid_Noより大きく(違う != という条件でもよいのですが、同じ結果の組が2つ出てきてしまう-たとえばid=1とid=6の組、id=6とid=1の組の2つ-ので不等号の条件にしています…念のため)、名前が同じという条件にすると、c1とc2の組み合わせで、上記に示したような結果が得られるというわけ。

ですが、ルールの条件部を先入観を持たずに見てみると、単純に例題の「違う欄に同じ氏名が入力されていたら」を記述しただけ (注1)ということがわかると思います。

以上、例題を通して、ルールベースプログラミングの特徴 (の一つ) を見てきました。最初にも書いたようにBRMSの最近の適用例はあまりルールベースプログラミングの特徴が表れにくいものが多いのですが、例題としてもあげたように、ところどころルールベース的に書くべきところも散見されるので今回、とりあげてみました。

さて、これを言ってしまうと、場合によってはルールベースの敷居の高さを感じてしまう方もおられるかもしれませんが、そもそもルールベースプログラミングは、通常の手続き型のプログラミングとはパラダイムが違います。ただ、いわゆる関数型のパラダイムの(Haskellの)モナドとか、論理型で言えばPrologのカットオペレータとか、わかりにくい概念というものはなく(注2)、「if-thenルールの条件にマッチするものを実行する」という人間の素朴な直感にマッチしたパラダイムなのでそれほど身構える必要はありません。むしろ手続き型のように、「どのように」実行するかを書いていく必要がない分、ルールベースでは単刀直入に「何を行うか」を書けるのでわかりやすいとも言えましょう。もっとも、だからこそBRMSが流行っているのだとは思いますが。

(注1) 名前が空白でないという追加の条件は加えています。
(注2) 強いて言えば競合解消戦略がわかりにくいかと思いますが、最近はあまり競合解消戦略に依存するようなルールの書き方はしないので、あまり問題にはならないでしょう。

「超高速開発」だけがBRMSのメリットか?

昨年あたりから、「超高速開発」のツールとしてBRMSが取り上げられることが多いのですが、BRMSは本当に「超高速開発」のツールなのでしょうか。

確かに、開発のスピードを速めることになるのは確かなのですが、どうも「超高速開発」ばかりが強調されていて、BRMSを用いた開発の他のさまざまな側面が忘れ去られているように思えるのは私だけでしょうか。

BRMSによる開発、もう少し広く BRA (Business Rule Approach) による開発の視点から考えると、少なくとも次のような特徴をあげることができます。

1.ビジネスルールを「見える化」できる。

従来の手法ですと、企業におけるさまざまな業務が拠って立つビジネスルールは、一旦、仕様書/設計書に落とされ、さらにシステムに実装されたときにはCOBOL、Java、C#など、プログラムのソースコードに埋め込まれてしまいます。実際にシステムを利用する業務担当者は、実装されているビジネスルールを仕様書を通してしか見ることができません。一方、システムを実装する担当者は、仕様書に書かれているビジネスルールをプログラムのソースコードに変換してシステムとして実装することになります。業務担当者とシステム実装者との間には、仕様書/設計書を通しての何段かの伝言ゲームが存在し、有名な下の図 ( University of London Computer Center Newsletter, No.53, March 1973 から*注 ) のような要求の食い違いが表れやすくもなってくることでしょう。

仕様伝達の食い違い
もし、業務担当者がプログラミングの素養があり、プログラムのソースコードを参照できるようであれば多少なりとも事態は改善するかもしれません。が、一般的なプログラミング言語で実装したソースコードは、通常、ビジネスルールの実装部分が明確に分離されていることは少なく、肝心なビジネスルールがどこに記述されているかを特定することそのものが困難であるというのは、実務でプログラムを書いたことがある人であれば誰しも同意するのではないでしょうか。

これに対し、BRMSによる開発は、ビジネスルールの実装を、システム上のロジックや、UIに関するロジックなどから明確に分離し、表(デシジョンテーブル)の形、あるいはDSLを用いて自然言語に近い形などで表現することで、業務担当者からシステムの担当者まで皆がビジネスルールの実装そのものを見てコミュニケーションができるようになることを目指すものであり、業務上のビジネスルールをより直截に実装しようとするものです。
もうちょっと言うと、そもそも、BRAという方法論は、STEPの原則にもみられるように、上記のような問題に対して正面から対峙するものであって、「超高速開発」というのは、むしろ、ビジネスルールが直截的に実装できることになったことによる副次的なものと言ってもいいかもしれません。

さらに、「見える化」という特徴については、おもしろい使い方もあって BRMS(ルールベース技術) を技術継承の道具として使う使い方などもあったりします(たとえばこんなサービスもあります)。製造業などではプラントの操作など、いたるところに職人的な専門家がいることが多いですが、その場合そのノウハウは、たいてい、明示的に文書化されておらず、専門家の頭の中に暗黙知として埋蔵されています。ルールベースによる技術継承では、この暗黙知を「ルール」として記述していくことになるのですが、単純に記述するだけでは文書としてルールを記述するのと何が違うの?という話になるでしょう。さて、その違いは・・・というと、それは記述したルールがそのまま実行できるというところにあります。
記述したルールがそのまま実行できると、次のようなことが可能になります。

  1. 専門家にヒアリングする、もしくは専門家自身がざっとルールを書き出す。
  2. それらのルールをルールベースに実装する。
  3. 専門家の選んだいくつかの事例に対して、上記で実装したルールベースを実行して結果を得る。
  4. 専門家に結果を検証してもらい、足りないと思われるルールを引き出す。
  5. 2.に戻って上記手順を繰り返し、ルールを精緻化していく。

ここでのミソは、ルールを実行してシミュレーションすることによって、すでに実装されているルールから論理的に帰結できることは新たにルールとして現れず、本質的に足りないルールが浮き彫りになるというところにあります。誰しも仕事をやるときに、自分がどんなルールを使ってやっているかなど、あまり意識はしていないでしょう。専門家も例外ではありません。思いつくルールをいくつか書き出すという程度なら書けるとは思いますが、これで十分かと問われるとおそらく自信を持って答えられないと思います。このときルールベース技術を用いれば、思いついたルールを「実行できる」形で表現でき、ルールを引き出す上で、「シミュレーション」という道具を手に入れることができます。これによって過不足なくルールを引き出すということができるわけですね。
これなどは BRMS をシステム開発のツールとして使うというよりも、むしろナレッジマネジメントの道具として使うというおもしろい例になるでしょう。

2.変化に対応しやすい

「超高速開発」ということで開発のときの速さがクローズアップされがちなBRMSですが、BRMSはシステムの稼働後にルールを状況に合わせて変更していくということも容易です。1.であげたようにビジネスルールが「見える化」されることで、どこを変更すればよいかが一目瞭然となるだけでなく、実際の変更も、BRMSの場合、仕様≒実装なだけに簡単にできます。オフィス風景

これは、業務担当者(ユーザ)自身がビジネスルールを実装する体制になっている場合に特に如実にあらわれます。従来ですと、ルールの仕様が変更されると自社のシステム部門を通し、さらに外注にルールの実装の変更をお願いしたりするなど、何重ものフィルタを通して仕様の変更を伝えるのが普通です。さらなるおまけとして費用もかかるとなるとなかなか簡単にはいかないのが従来のやり方。また、単純にロジック的なルールの変更が発生するというだけでなく、それにまつわる社内、社外的な手続きなども発生するので、結構な労力になり、心理的な障壁も高くなってしまうのは、たいてい誰しもが経験するところ。

それが、業務担当者が直接手を下すことができるとなると、ルール仕様の伝達に際しての中抜きができるだけでなく(というよりも伝達の必要もない)、仕様変更に伴う有形無形の間接的な作業も減るので、ルール仕様の変更に対しての障壁が目に見えて低くなります。

さて、このように仕様の変更に対する物理的、心理的な障壁が低くなってくることで何が起きるでしょう。状況や時流に合わせてルールが頻繁に変更されるようになり、次第に、むしろ積極的に、まわりに合わせてルールを変更するようになってくるのではないでしょうか。

実際、近年、米国で盛んに言われている概念 「デシジョンマネジメントシステム(Decision Management System)」は、BRMSのこの変更の容易さがひとつの前提になっています。日常の業務の中ではさまざまな判断(デシジョン)があらわれます。簡単なところでは書類のチェックとか、もう少し複雑なところでは何らかの(たとえば保険に入れるかどうかの)査定とか…。

デシジョンマネジメントシステムは、こういった日常頻繁に起こる判断業務の自動化(オートメーション)を目指します。

こういった書類のチェックとか、査定などの判断業務は基準も比較的明確でルール化しやすいものです。しかし、とは言っても現在のルールの条件から漏れてしまうケースがないとは限りません。デシジョンマネジメントシステムでは、こういったいままでルールから漏れてきたケースなどにも対処できるように、データ分析や機械学習、シミュレーション等々周辺の技術も動員してルールによる実行結果を吟味し、その内容を元にルールを変更したり、新たなルールを追加したりすることで、判断業務の自動化水準を高めてより多様なケースに自動で対応できるようにしていきます。

…若干話が脱線しましたが、このように先進的な取り組みの中では、BRMSのルールの変更のしやすさは、すでに前提となっていて、それをもとに状況に合わせて積極的にルールを変更するという方向で考えられるようになっています。

今の時代、どこの会社にもいる事務担当者ですが、近い将来の事務担当者の業務は、個々のケースをひとつひとつ手で処理するのではなく、典型的なケースはシステムで自動処理し、典型的でないケースを手で処理すると同時に、それらのケースも自動で処理できるようにルールを調整する…というようなことになるかもしれませんね。

注:有名な絵なのでご存知の方も多いかと思いますが、その簡単な解説は、たとえば

http://labo.mamezou.com/opinion/op_000/op_000_003.html

などをご参考に。

もう、こんなことを決めなくてはいけないの? -BRMSのプロジェクトにて-

BRMSを用いた開発プロジェクトは規模が大きいと、多くの場合、

・画面等を中心としたアプリケーション開発チーム
・BRMSを用いたルール開発チーム

に別れて、ウォーターフォール的に進められますが、その場合、BRMSチームの方は従来の一般的なプロジェクトよりも、ずっと早い段階で業務的な仕様の核心に入ってしまいます。
ユーザとの打ち合わせで、BRMSチームがルールで使う項目や条件についてヒアリングしようとすると、業務ユーザに
「もう、こんなことを決めなくてはいけないの?」
と、面食らわれることがままあったり…。

従来のウォーターフォール開発ですと、要件定義、基本設計、詳細設計、プログラミング…と、順次詳細に入っていくので、詳細設計くらいの段階でようやくルールの条件や条件に使われる項目の明確な意味が必要になってきます。

仮にアジャイルな開発手法を使っても、ふつうは画面を中心に使い勝手や画面間のつながりなどプロセス的な側面を、まずチェックするので、業務のルールを実装するのは若干、後に。

さて、BRMSですが、これは業務ルールそのものを単刀直入に実装します。その前に設計を…と言っても、

・業務で使っている用語(項目)をきちんと定義します。
・上記の用語(項目)を使って、業務のルールを記述します。

というくらい。これを実装するにしても、上記にしたがって、用語(項目)、ルールを定義してあとは、

・用語(項目)に具体的なデータを設定して実行してみて、結果が想定とあっているか確認します。

これだけ。
論理的に考えれば当たり前といえば当たり前なのですが、従来ですと、仮に業務のルールにあたるプログラムができたとしても、これを動かすためには、データの入出力を担う画面が必要になったり、同じくデータの入出力を司るテスト用のドライバプログラムが必要になったりするのが普通。それに対し、BRMSは、業務のルール(ロジック)を切り出して、独立して動かしテストできます(cf.STEPの原則)。

私の思うに、これは重要なBRMSのメリットのひとつ。本来やりたいことが、業務処理の何らかの(半)自動化ということであれば、処理のしたがう業務ルールを早い段階で実装、動かすことができることは、事前に業務ロジックのバグをつぶしたりするのに大きな威力を発揮することでしょう。実際、以前のプロジェクトで、ユーザが、社内文書としてまとまっていた査定基準を、ルールの形に実装したことで、実は隠れていた査定基準の不備まで発見してしまったということも聞きました。

「もう、こんなことを決めなくてはいけないの?」

…「そうなんです。でないと、BRMSチームはやることがなくなるんです。」
…「でも、決めていただければ、すぐにでも業務のロジックを動かして、テストできますよ。」

Red Hat フォーラム 2013…生産計画とBRMS

金曜日に、Red Hat フォーラム 2013 に行ってきました。もちろんJBoss BRMS に関連するセッションを聞いてきたのですが、今回の一番の収穫は、生産計画にBRMSを適用してうまくいった事例を聞けたこと。

今まで、生産計画にBRMSというのは、昔の経験もあって個人的には二の足を踏む感があったのですが、実際成功事例を聞いてみると、ケースによっては案外行けるかも…と思わせるものでした。

話を聞いてみると、制約条件がかなり厳しいらしく、想像するに、専門家の「コツ」とかいった、アドホックで適用するべき状況の曖昧な、いわゆる「ガイドライン」的なルールが入る余地がないというケースのようで、こんな場合なら結構アリかも…。

生産計画の場合、ルールによって生成・更新された新たなファクトが、さらにルールの発火を促す…という推論過程が発生するため、ルールエンジンとしては、きちんと推論機能を持ったエンジンでないと適用は難しいかと思います。

しかし、それだけに、生産計画は、現在、BRMS適用の主流となっている、(ロジック的には単純な)、データの妥当性チェックや査定などの分野を超え、ロジックとして一段高いレベルでの適用分野となり、BRMSとしての特徴がより発揮されてくるようになるのではないでしょうか。

OpenRules入門

オープンソースのBRMSとして、OpenRulesを試してみました。Drools もBRMSとして完成度が高く、さまざまな高度な技もできたり柔軟な対応ができますが、一方でDroolsでルールを実際に動かすまでにはそれなりに準備が必要で、(プロジェクトをきっちり行うという場合にはあまり問題になりませんが、)ちょっと気軽にルールを作って動かすというのには、個人的には若干敷居を高く感じてしまいます。

いわゆるReteアルゴリズムベースのルールエンジンの動きを学ぶだけであれば、むしろ、ちょっと古いですが、とりあえずダウンロードして動かすだけで、すぐにルールを書き始められるClipsや、もっと遡ってOPS5といったようなルールベースの他にあまり余分な要素のない言語の方が(私見ではありますが)学びやすいような気もします。

OpenRulesは、エンジンの動きこそReteベースではなく、デシジョンテーブルを上から順になめて条件の一致したルールを実行するという単純な動きですが、Excelでテーブルを作るだけで大部分が完結してしまうという敷居の低さ(特にユーザが直接ルールを書く場合など)は、ちょっと興味のあるところです。

というわけで本家のサイトの方でOpenRules 入門という記事を書き始めました。もし興味がおありであればどうぞ。

OpenRules入門

アシストが「Corticon」の国内総販売代理店に

若干、記事のアップ時期を逸した感がありますが、

アシスト、BRMSソフト「Corticon」の国内総販売代理店に

Corticonは、デシジョンテーブルを使ってルールを記述し、ビジネスユーザにとっても使いやすいBRMS。実際、日本国内でも、カスタマイズなしの生の開発環境(IDE)そのままに、ユーザ自身がルールを開発している事例もあります。今後、どのように展開していくのか、楽しみなところです。

暦とルールと優先度

巷のBRMSが採用しているルールエンジンの動きとしては、大きく分けて2つの流れがあります。

  1. 通常の手続き型言語のIF文と同じように上から順に判断するエンジン。
  2. 現在保持しているデータの状態に対し、その状態を満足するルールを次々と処理していくReteアルゴリズムを代表とするデータ駆動のエンジン。

代表的なBRMSは、たいてい上記 2 の動きをサポートしていますが、通常の手続き型の動きとちょっと違うところがあるので、このブログでも折に触れこういった動きの特徴となるところを取り上げていきたいと思います。

今日は優先度(salience)について。通常の手続き型の言語ですと、上から順番に処理するだけで、優先度も何もない(強いて言えば上に行くほど優先度が高い)のですが、データ駆動のルールエンジンですと、優先度が時に重要な役割を果たします。ということで、本日はうるう年の判定を優先度を使って考えてみたいと思います。

ビッグベン
もっとも、うるう年の判定など、あえてルールでやらずとも普通の言語であればライブラリを使って一発なのですが、たまたま最近読んだ本(数量化革命の中にユリウス暦から現在のグレゴリオ暦を採用するまでの経緯を記したくだりがあり、うるう年の歴史的背景とからめて順にルールで実装していくというのもおもしろいかと思い、ちょっと手すさび程度に試してみたという次第。気楽にお付き合いください。

(ちなみに、この本はヨーロッパ帝国主義が成功をおさめた理由のひとつを、人々の世界観・思考様式が数量化・視覚化に依拠したものに変化したことによるとし、数字、機械時計、楽譜、遠近法、複式簿記などを例にとりながら説明していくという本。私は結構面白く読めました)

さて、グレゴリオ暦法では、うるう年を次のように決めています。

  1. 西暦年号が4で割り切れる年をうるう年とする。
  2. 【1】の例外として、西暦年号が100で割り切れて400で割り切れない年は平年とする。
    (国立天文台の質問ページから)

以上の規則は、通常の言語で手で記述しても大した話ではなく、たとえばJavaだったら yearを判定対象の年として条件を

year % 4 == 0 && year % 100 != 0 || year % 400 == 0

と書いてしまえばよい話なので、Droolsのルールでもそのまま

Leapyear.drl

rule "JuliusLeap"
     salience 100
     when
         $year : Year( leapYear == Year.NOT_YET
             && ((year % 4) == 0
             && (year % 100) != 0
             || (year % 400) == 0))
     then
         $year.setLeapYear(Year.LEAP_YEAR);
            // うるう年判定用の属性にうるう年をセットする
         update($year);
            // Droolsのワーキングメモリ内のファクトとして更新
         System.out.println("うるう年です");
end

rule "JuliusNotLeap"
     when
         $year : Year(leapYear == Year.NOT_YET)
     then
         $year.setLeapYear(Year.COMMON_YEAR);
         update($year);
         System.out.println("うるう年ではありません");
end

と書いてしまえば用が足りてしまいます。でも、これではあまりにもあっさりしすぎているので、以下では、もう少しルールらしさが表れる方法で実装してみましょう。

(なお、ここでは、以下の Year クラスを使いました。

public static class Year {
    public static final int NOT_YET = 0;       // 未定
    public static final int COMMON_YEAR = 1;   // 通常の年
    public static final int LEAP_YEAR = 2;     // うるう年

    private int year;                       // 西暦
    private int leapYear;                   // うるう年か否か

    ... (略)
}

また、上記ルールですでに優先度(salience)が出てきていますが、これはうるう年のYearインスタンスがあった場合、両方のルールにマッチするので、うるう年の条件にマッチするのであればそちらを優先して処理するということになります)

上で、うるう年の判定条件は、

  1. 西暦年号が4で割り切れる年をうるう年とする。
  2. 【1】の例外として、西暦年号が100で割り切れて400で割り切れない年は平年とする。

と書きましたが、現在のグレゴリオ暦の元となるユリウス暦では、うるう年といえば1の条件のみでした。ユリウス暦はご存じのとおりユリウス・カエサルが定めた暦法で、紀元前45年に始まり、1年を365.25日として暦が作られています。したがって4年に1回、1日分の補正が入るわけですね。

このユリウス暦によるうるう年の判定条件をルールで書くと

Leapyear1.drl

rule "JuliusLeap"
    salience 100
    when
        $year : Year( leapYear == Year.NOT_YET
                      && (year % 4) == 0)
    then
        $year.setLeapYear(Year.LEAP_YEAR);
        update($year);
        System.out.println("うるう年です");

end

rule "JuliusNotLeap"
    when
        $year : Year(leapYear == Year.NOT_YET)
    then
        $year.setLeapYear(Year.COMMON_YEAR);
        update($year);
        System.out.println("うるう年ではありません");

end

となります。

ユリウス暦はそのはじめから1600年以上も続いたわけですが、現在の技術による観測によれば1年は、365.242 189 572日(2013年年央値)。1年を365.25日とちょっと長めにとっているユリウス暦は長い間には誤差が累積していくわけで、グレゴリオ暦を採用することになった1582年には、誤差が11日に達していました。多くのアバウトな人にとっては、まあ別にいいのでは…ということでしたが、敬虔なキリスト教徒にとっては結構な大問題だったようです。

というのもキリストの復活を祝う復活祭。これは「春分の日以降の最初の満月のあと最初の日曜日」と定められていましたが、ユリウス暦上の春分の日である3月21日が天文学的にみた春分の日と10日以上もずれていると、今祝っている復活祭が誤った日に行われているのではないかという疑念がでてきます。

そこで、当時の法王グレゴリウス13世が、1582年に有識者を集めて暦の改革をします。(ちなみに1582年と言えば、日本では「本能寺の変」の年です。)

そのころの観測データでは、1年は365.2425日=365+97/400日という値が得られており、400年のうち97回、閏年があれば十分で、400年に100回の閏年を入れるユリウス暦では400年のうちに3日分あまってしまっていくというわけ。

では、どうしたかというと、4年に1回の閏年のうち、100年に1回は閏年にしない・・・これでは、400年に96回の閏年になってしまい、ちょっと削りすぎなので、100年に1回の閏年でない年のうち、400で割り切れる年は特別に閏年とする。

といういうことにして、400年に97回の閏年を実現したわけです。さて、まずは、この例外事項のみをルールに書いてみましょう。

rule "Gregorio1"
    when
        $year : Year( leapYear == Year.NOT_YET
                      && (year % 100) == 0
                      && (year % 400) != 0 )
    then
        $year.setLeapYear(Year.COMMON_YEAR);
        update($year);
        System.out.println("うるう年ではありませんよ");

end

ルールの書き方としては、400年の条件と100年の条件をさらに分けることもできますが、あまり分けすぎてもわかりにくくなるので、ここではひとまとめのままにしておきます。

上のルールに優先度を加え、例外事項のルールとして付け加えることで、もともとあったルールをそのままに判定条件の修正ができるようになります。上のルールは他のルールの条件に比べ、より厳しい例外的な条件になるので、もしこの条件に合うケースがあった場合には最優先で処理をするという意味で、もともとあった優先度(100)以上の優先度(200)をつけています)

Leapyear2.drl

rule "JuliusLeap"
    salience 100
    when
        $year : Year( leapYear == Year.NOT_YET
                      && (year % 4) == 0)
    then
        $year.setLeapYear(Year.LEAP_YEAR);
        update($year);
        System.out.println("うるう年です");

end

rule "Gregorio1"
    salience 200
    when
        $year : Year( leapYear == Year.NOT_YET
                      && (year % 100) == 0
                      && (year % 400) != 0 )
    then
        $year.setLeapYear(Year.COMMON_YEAR);
        update($year);
        System.out.println("うるう年ではありませんよ");

end

rule "JuliusNotLeap"
    when
        $year : Year(leapYear == Year.NOT_YET)
    then
        $year.setLeapYear(Year.COMMON_YEAR);
        update($year);
        System.out.println("うるう年ではありません");

end

これでグレゴリオ暦の閏年の判定条件ルールができました。このように優先度をうまく使うと、元々あるルールをそのままに、あとから比較的見通しよく例外ルールを付け加えることができます。

ところで、閏年の判定に関して、実はちょっと前におもしろいブログ記事を見つけました。

西暦1000年は閏年かそうじゃないのか?

グレゴリオ暦の判定条件からすれば閏年でないはずなのですが、Javaでは閏年になり、MySqlの判定では閏年になっていないとのこと。これは如何に。

西暦1000年と言えば、上にのべた歴史的背景から考えると、そもそもグレゴリオ暦などない時代。したがって上のルールの1のみが適用されればよいので、Javaでは閏年と判断しているのでしょう。またルールとしても、西暦1000年においては、2のルールは全く存在していないので、ルールの判定条件として2にあたるルールの条件に1582年以降という条件を加えればよいことになります。

では、MySqlの判定条件は間違っているということでしょうか。これについては、

先発グレゴリオ暦

という考え方もあり、国際規格(ISO8601)でも定められているようなので、一概に間違いとは言えず、単なる立場の違いと考えた方がよいのでしょう。

***

今回は、優先度を用いて、一般に適用されるルールと、その中で例外的に適用されるルールの振り分けを行ってみました。データ駆動の処理の動きは、このように手続き型とちょっと違う特徴を持っています。こういった特徴を表す実例はまだ他にもあるのですが、長くなるのでまた別の機会に記事にしようと思います。では、また次回に。