Javaでシンプルなビジネスルールエンジンを作成するさまざまな方法を模索しています。クライアントに一連のルールを設定できる単純なwebappを提示する必要があります。ルールベースのサンプルは次のようになります。
以下に例を示します。
IF (PATIENT_TYPE = "A" AND ADMISSION_TYPE="O")
SEND TO OUTPATIENT
ELSE IF PATIENT_TYPE = "B"
SEND TO INPATIENT
ルールエンジンは非常にシンプルで、最終的なアクションは入院患者または外来患者に送信する2つのアクションのうちの1つだけです。式に関係する演算子は=,>,<,!=
および式間の論理演算子はAND, OR and NOT
。
ユーザーがtextarea
に小さなスクリプトを記述するWebアプリケーションを構築し、式を評価します。この方法で、ビジネスルールは簡単な英語で説明され、ビジネスユーザーはロジックを完全に制御できます。
これまでに行った調査から、ANTLR
に出会い、この問題を解決するための可能なオプションとして独自のスクリプト言語を作成しました。 Droolsルールエンジンのようなオプションを検討していません。ここではやり過ぎだと感じているからです。この種の問題を解決した経験はありますか?はいの場合、どのようにそれについて行きましたか?
Javaで簡単なルールベースの評価システムを実装するのはそれほど難しいことではありません。おそらく式のパーサーは最も複雑なものです。以下のコード例では、いくつかのパターンを使用して必要な機能。
シングルトンパターンは、使用可能な各操作をメンバーマップに格納するために使用されます。操作自体はコマンドパターンを使用して柔軟な拡張性を提供しますが、有効な式のそれぞれのアクションはディスパッチパターンを使用します。最後に、各ルールの検証にはインタープリターパターンが使用されます。
上記の例で示したような式は、操作、変数、値で構成されています。 wiki-example を参照して、宣言できるものはすべてExpression
です。したがって、インターフェースは次のようになります。
_import Java.util.Map;
public interface Expression
{
public boolean interpret(final Map<String, ?> bindings);
}
_
Wikiページの例ではint(計算機を実装します)を返しますが、式がtrue
に評価される場合に式がアクションをトリガーするかどうかを決定するために、ここでブール値の戻り値のみが必要です。
式は、前述のように、_=
_、AND
、NOT
、...などの操作、またはVariable
またはそのValue
。 Variable
の定義は以下のとおりです。
_import Java.util.Map;
public class Variable implements Expression
{
private String name;
public Variable(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
@Override
public boolean interpret(Map<String, ?> bindings)
{
return true;
}
}
_
変数名の検証はあまり意味がないため、デフォルトではtrue
が返されます。 BaseType
のみを定義する際にできるだけ汎用的に保持される変数の値についても同様です。
_import Java.util.Map;
public class BaseType<T> implements Expression
{
public T value;
public Class<T> type;
public BaseType(T value, Class<T> type)
{
this.value = value;
this.type = type;
}
public T getValue()
{
return this.value;
}
public Class<T> getType()
{
return this.type;
}
@Override
public boolean interpret(Map<String, ?> bindings)
{
return true;
}
public static BaseType<?> getBaseType(String string)
{
if (string == null)
throw new IllegalArgumentException("The provided string must not be null");
if ("true".equals(string) || "false".equals(string))
return new BaseType<>(Boolean.getBoolean(string), Boolean.class);
else if (string.startsWith("'"))
return new BaseType<>(string, String.class);
else if (string.contains("."))
return new BaseType<>(Float.parseFloat(string), Float.class);
else
return new BaseType<>(Integer.parseInt(string), Integer.class);
}
}
_
BaseType
クラスには、特定のJavaタイプの具体的な値タイプを生成するファクトリメソッドが含まれています。
Operation
はAND
、NOT
、_=
_、...のような特別な式になりました。抽象基底クラスOperation
は左を定義しますオペランドとしての右オペランドは、複数の式を参照できます。 F.e. NOT
は、おそらく右辺の式のみを参照し、検証結果を否定するため、true
はfalse
に、またはその逆になります。ただし、反対側のAND
は、左と右の式を論理的に組み合わせて、検証時に両方の式がtrueになるようにします。
_import Java.util.Stack;
public abstract class Operation implements Expression
{
protected String symbol;
protected Expression leftOperand = null;
protected Expression rightOperand = null;
public Operation(String symbol)
{
this.symbol = symbol;
}
public abstract Operation copy();
public String getSymbol()
{
return this.symbol;
}
public abstract int parse(final String[] tokens, final int pos, final Stack<Expression> stack);
protected Integer findNextExpression(String[] tokens, int pos, Stack<Expression> stack)
{
Operations operations = Operations.INSTANCE;
for (int i = pos; i < tokens.length; i++)
{
Operation op = operations.getOperation(tokens[i]);
if (op != null)
{
op = op.copy();
// we found an operation
i = op.parse(tokens, i, stack);
return i;
}
}
return null;
}
}
_
おそらく2つの操作が目に飛び込んできます。 int parse(String[], int, Stack<Expression>);
は、有効な操作をインスタンス化するために必要なものを最もよく知っているため、具体的な操作を解析するロジックをそれぞれの操作クラスにリファクタリングします。 Integer findNextExpression(String[], int, stack);
は、文字列を式に解析する際に操作の右側を見つけるために使用されます。式の代わりにここでintを返すのは奇妙に聞こえるかもしれませんが、式はスタックにプッシュされ、ここの戻り値は作成された式で使用される最後のトークンの位置を返します。したがって、int値は、既に処理されたトークンをスキップするために使用されます。
AND
操作は次のようになります。
_import Java.util.Map;
import Java.util.Stack;
public class And extends Operation
{
public And()
{
super("AND");
}
public And copy()
{
return new And();
}
@Override
public int parse(String[] tokens, int pos, Stack<Expression> stack)
{
Expression left = stack.pop();
int i = findNextExpression(tokens, pos+1, stack);
Expression right = stack.pop();
this.leftOperand = left;
this.rightOperand = right;
stack.Push(this);
return i;
}
@Override
public boolean interpret(Map<String, ?> bindings)
{
return leftOperand.interpret(bindings) && rightOperand.interpret(bindings);
}
}
_
parse
では、おそらく左側から既に生成された式がスタックから取得され、右側が解析されて再びスタックから取得され、最終的に新しいAND
操作がプッシュされることがわかります。左と右の両方の式を含み、スタックに戻ります。
NOT
はその場合に似ていますが、前述のように右側のみを設定します。
_import Java.util.Map;
import Java.util.Stack;
public class Not extends Operation
{
public Not()
{
super("NOT");
}
public Not copy()
{
return new Not();
}
@Override
public int parse(String[] tokens, int pos, Stack<Expression> stack)
{
int i = findNextExpression(tokens, pos+1, stack);
Expression right = stack.pop();
this.rightOperand = right;
stack.Push(this);
return i;
}
@Override
public boolean interpret(final Map<String, ?> bindings)
{
return !this.rightOperand.interpret(bindings);
}
}
_
_=
_演算子は、interpret
メソッドの引数として提供されるバインディングマップの特定の値と実際に等しい場合、変数の値を確認するために使用されます。
_import Java.util.Map;
import Java.util.Stack;
public class Equals extends Operation
{
public Equals()
{
super("=");
}
@Override
public Equals copy()
{
return new Equals();
}
@Override
public int parse(final String[] tokens, int pos, Stack<Expression> stack)
{
if (pos-1 >= 0 && tokens.length >= pos+1)
{
String var = tokens[pos-1];
this.leftOperand = new Variable(var);
this.rightOperand = BaseType.getBaseType(tokens[pos+1]);
stack.Push(this);
return pos+1;
}
throw new IllegalArgumentException("Cannot assign value to variable");
}
@Override
public boolean interpret(Map<String, ?> bindings)
{
Variable v = (Variable)this.leftOperand;
Object obj = bindings.get(v.getName());
if (obj == null)
return false;
BaseType<?> type = (BaseType<?>)this.rightOperand;
if (type.getType().equals(obj.getClass()))
{
if (type.getValue().equals(obj))
return true;
}
return false;
}
}
_
parse
メソッドからわかるように、値は変数に割り当てられ、変数は_=
_シンボルの左側にあり、値は右側にあります。
さらに、解釈では、変数バインディングで変数名が使用可能かどうかがチェックされます。利用できない場合、この用語はtrueに評価できないため、評価プロセスをスキップできます。存在する場合、右側(= Value部分)から情報を抽出し、最初にクラスタイプが等しいかどうかを確認します。等しい場合、実際の変数値がバインディングと一致するかどうかを確認します。
式の実際の解析は操作にリファクタリングされるため、実際のパーサーはかなりスリムになります。
_import Java.util.Stack;
public class ExpressionParser
{
private static final Operations operations = Operations.INSTANCE;
public static Expression fromString(String expr)
{
Stack<Expression> stack = new Stack<>();
String[] tokens = expr.split("\\s");
for (int i=0; i < tokens.length-1; i++)
{
Operation op = operations.getOperation(tokens[i]);
if ( op != null )
{
// create a new instance
op = op.copy();
i = op.parse(tokens, i, stack);
}
}
return stack.pop();
}
}
_
ここで、copy
メソッドはおそらく最も興味深いものです。解析はかなり一般的であるため、現在どの処理が処理されているかは事前にはわかりません。登録済みの操作の中から見つかった操作を返すと、このオブジェクトが変更されます。式にその種類の操作が1つしかない場合、これは重要ではありません-ただし、複数の操作(2つ以上の等値操作)がある場合、その操作は再利用されるため、新しい値で更新されます。これにより、その種類の以前に作成された操作も変更されるため、操作の新しいインスタンスを作成する必要があります-copy()
はこれを実現します。
Operations
は、以前に登録された操作を保持し、操作を指定されたシンボルにマップするコンテナです。
_import Java.util.HashMap;
import Java.util.Map;
import Java.util.Set;
public enum Operations
{
/** Application of the Singleton pattern using enum **/
INSTANCE;
private final Map<String, Operation> operations = new HashMap<>();
public void registerOperation(Operation op, String symbol)
{
if (!operations.containsKey(symbol))
operations.put(symbol, op);
}
public void registerOperation(Operation op)
{
if (!operations.containsKey(op.getSymbol()))
operations.put(op.getSymbol(), op);
}
public Operation getOperation(String symbol)
{
return this.operations.get(symbol);
}
public Set<String> getDefinedSymbols()
{
return this.operations.keySet();
}
}
_
列挙型シングルトンパターンの横には、ここで特に空想するものはありません。
Rule
には、評価時に特定のアクションをトリガーできる1つ以上の式が含まれるようになりました。したがって、ルールは、以前に解析された式と、成功した場合にトリガーされるアクションを保持する必要があります。
_import Java.util.ArrayList;
import Java.util.List;
import Java.util.Map;
public class Rule
{
private List<Expression> expressions;
private ActionDispatcher dispatcher;
public static class Builder
{
private List<Expression> expressions = new ArrayList<>();
private ActionDispatcher dispatcher = new NullActionDispatcher();
public Builder withExpression(Expression expr)
{
expressions.add(expr);
return this;
}
public Builder withDispatcher(ActionDispatcher dispatcher)
{
this.dispatcher = dispatcher;
return this;
}
public Rule build()
{
return new Rule(expressions, dispatcher);
}
}
private Rule(List<Expression> expressions, ActionDispatcher dispatcher)
{
this.expressions = expressions;
this.dispatcher = dispatcher;
}
public boolean eval(Map<String, ?> bindings)
{
boolean eval = false;
for (Expression expression : expressions)
{
eval = expression.interpret(bindings);
if (eval)
dispatcher.fire();
}
return eval;
}
}
_
ここでは、同じアクションに必要な場合に複数の式を追加できるようにするために、構築パターンが使用されています。さらに、Rule
はデフォルトでNullActionDispatcher
を定義します。式が正常に評価されると、ディスパッチャはfire()
メソッドをトリガーし、検証が成功したときに実行するアクションを処理します。ここでは、true
またはfalse
検証のみを実行する必要があるため、アクションの実行が不要な場合にnull値を処理しないように、nullパターンが使用されます。したがって、インターフェイスもシンプルです。
_public interface ActionDispatcher
{
public void fire();
}
_
あなたのINPATIENT
またはOUTPATIENT
アクションがどうあるべきか本当にわからないので、fire()
メソッドはSystem.out.println(...);
メソッド呼び出しのみをトリガーします:
_public class InPatientDispatcher implements ActionDispatcher
{
@Override
public void fire()
{
// send patient to in_patient
System.out.println("Send patient to IN");
}
}
_
最後になりましたが、コードの動作をテストする簡単なメインメソッド:
_import Java.util.HashMap;
import Java.util.Map;
public class Main
{
public static void main( String[] args )
{
// create a singleton container for operations
Operations operations = Operations.INSTANCE;
// register new operations with the previously created container
operations.registerOperation(new And());
operations.registerOperation(new Equals());
operations.registerOperation(new Not());
// defines the triggers when a rule should fire
Expression ex3 = ExpressionParser.fromString("PATIENT_TYPE = 'A' AND NOT ADMISSION_TYPE = 'O'");
Expression ex1 = ExpressionParser.fromString("PATIENT_TYPE = 'A' AND ADMISSION_TYPE = 'O'");
Expression ex2 = ExpressionParser.fromString("PATIENT_TYPE = 'B'");
// define the possible actions for rules that fire
ActionDispatcher inPatient = new InPatientDispatcher();
ActionDispatcher outPatient = new OutPatientDispatcher();
// create the rules and link them to the accoridng expression and action
Rule rule1 = new Rule.Builder()
.withExpression(ex1)
.withDispatcher(outPatient)
.build();
Rule rule2 = new Rule.Builder()
.withExpression(ex2)
.withExpression(ex3)
.withDispatcher(inPatient)
.build();
// add all rules to a single container
Rules rules = new Rules();
rules.addRule(rule1);
rules.addRule(rule2);
// for test purpose define a variable binding ...
Map<String, String> bindings = new HashMap<>();
bindings.put("PATIENT_TYPE", "'A'");
bindings.put("ADMISSION_TYPE", "'O'");
// ... and evaluate the defined rules with the specified bindings
boolean triggered = rules.eval(bindings);
System.out.println("Action triggered: "+triggered);
}
}
_
ここでRules
はルールの単純なコンテナクラスであり、定義された各ルールにeval(bindings);
呼び出しを伝播します。
ここでの投稿はすでに長い道のりであるため、他の操作は含めませんが、必要に応じて自分で実装するのはそれほど難しくないはずです。さらに、おそらく独自のパッケージ構造を使用するため、パッケージ構造は含めませんでした。さらに、例外処理は含めませんでした。コードをコピーアンドペーストするすべての人にそれを任せます。
解析は、具体的なクラスではなく、パーサーで明らかに行われるべきだと主張するかもしれません。そのことは承知していますが、新しい操作を追加する場合は、1つのクラスに触れるだけでなく、パーサーと新しい操作を変更する必要があります。
ルールベースのシステムを使用する代わりに、ペトリネットまたは [〜#〜] bpmn [〜#〜] をオープンソースと組み合わせて Activiti Engine を使用して、このタスクを達成します。ここで、操作は言語内ですでに定義されています。具体的なステートメントを、自動的に実行できるタスクとして定義するだけで済みます。タスクの結果(つまり、単一のステートメント)に応じて、「グラフ」 。したがって、モデリングは通常、BPMN言語のXMLの性質に対処することを避けるために、グラフィカルエディターまたはフロントエンドで行われます。
基本的に...やらないで
なぜ理解するには:
私はそれが遠くから素晴らしいアイデアのように見えることを知っていますが、ビジネスルールエンジンは常にそれが書かれたプログラミング言語を維持、展開、デバッグするのが難しくなります-しないでくださいあなたがそれを助けることができるなら、あなた自身のプログラミング言語を準備してください。
私は元会社で個人的にその道を進んでおり、数年後にどこへ行くのかを見てきました(神が私たちを憎む平行した次元からまっすぐに来た言語で書かれたデータベースに座っている巨大なデバッグできないスクリプト適切なプログラミング言語ほど強力ではないと同時に、開発者が扱うにはあまりにも複雑で悪すぎるため、顧客の期待の100%を最終的に満たすことはありません(クライアントを気にしないでください)) 。
「ビジネスルールの適応」にプログラマーの時間を費やさないという考えに夢中になっている特定の種類のクライアントがいることを知っています。この方向で何かを作成する必要があります-しかし、あなたがすることはあなた自身のものを発明しません。
優れたツール(コンパイルを必要としないため、動的にアップロードできるなど)を備えたきちんとしたスクリプト言語が数多くあり、それらは滑らかにインターフェイスされ、Javaコードとテイクから呼び出すことができます実装したJava APIを利用可能にするAPIの利点、 http://www.slideshare.net/jazzman1980/j-Ruby-injavapublic#btnNext を参照してください。おそらくJythonも、
そしてクライアントがこれらのスクリプトの作成を断念すると、は失敗したレガシーを維持する幸せな任務に任せられます-thatlegacyできる限り痛みはありません。
Drools のようなものを使用することをお勧めします。独自のカスタムソリューションを作成すると、デバッグする必要があり、Droolsなどのルールエンジンが提供する機能よりも確かに少ない機能を提供するため、やり過ぎになります。 Droolsには学習曲線があることは理解していますが、カスタム言語やカスタムソリューションの作成と比較することはしません...
私の意見では、ユーザーがルールを書くためには、何かを学ぶ必要があります。 drools rule language より簡単な言語を提供できると思いますが、彼/彼女のニーズをすべて取り込むことは決してありません。 Droolsルール言語は、単純なルールに対して十分にシンプルです。さらに、整形式のドキュメントを提供できます。エンドユーザーによって作成され、システムに適用されるルールを制御する予定の場合、おそらく、doolsに適用されるルールを形成するGUIを作成する方が賢明でしょう。
私が助けたことを願っています!
Droolsよりも軽いが、同様の機能を備えたものを探している場合は、 http://smartparam.org/ projectを確認できます。プロパティファイルとデータベースにパラメータを保存できます。
次の2つの主な理由により、失敗するように設定しています。
1.を解くと、NLPのファジードメインにプッシュされます。OpenNLPなどのツールやそのエコシステムのツールを使用できます。ユーザーが物事を書き留める微妙に異なる方法が大量にあるため、より正式な文法に向かってあなたの思考が歪んでいることに気付くでしょう。この作業を行うと、DSLタイプのソリューションになるか、独自のプログラミング言語を設計する必要があります。
Scalaパーサコンビネータを使用して、自然言語とより形式化された文法の両方を解析する妥当な結果が得られました。問題は同じですが、それらを解決するために記述しなければならないコードは読みやすい.
要するに、非常に単純なルール言語を考えている場合でも、テストする必要があるシナリオの量を過小評価していることに気付くでしょう。 NeilAは、ルールのタイプごとに適切なUIを作成することにより、複雑さを軽減するようアドバイスする権利があります。あまりにも一般的にしようとしないでください、そうしないとあなたの顔に爆発します。
過去の経験から、「プレーンテキスト」ルールベースのソリューションは非常に悪い考えです。また、複数のルールを単純または複雑に追加する必要があるとすぐに、エラーの余地が大きくなります。デバッグ/保守/変更...
私がやった(そして非常にうまく機能する)ことは、抽象ルール(ルールのタイプごとに1つ)を拡張する厳密/具象クラスを作成することです。各実装は、必要な情報と、その情報を処理して希望する結果を得る方法を知っています。
Web /フロントエンド側で、そのルールに厳密に一致するコンポーネント(各ルール実装用)を作成します。次に、ユーザーに使用するルールのオプションを提供し、それに応じてインターフェースを更新します(ページのリロード/ javascriptにより)。
ルールが追加/変更されると、対応する実装を取得するためにすべてのルール実装を反復処理し、その実装でフロントエンドからの生データ(jsonの使用を推奨)を解析し、そのルールを実行します。
public abstract class AbstractRule{
public boolean canHandle(JSONObject rawRuleData){
return StringUtils.equals(getClass().getSimpleName(), rawRuleData.getString("ruleClassName"));
}
public abstract void parseRawRuleDataIntoThis(JSONObject rawRuleData); //throw some validation exception
public abstract RuleResult execute();
}
public class InOutPatientRule extends AbstractRule{
private String patientType;
private String admissionType;
public void parseRawRuleDataIntoThis(JSONObject rawRuleData){
this.patientType = rawRuleData.getString("patientType");
this.admissionType= rawRuleData.getString("admissionType");
}
public RuleResultInOutPatientType execute(){
if(StringUtils.equals("A",this.patientType) && StringUtils.equals("O",this.admissionType)){
return //OUTPATIENT
}
return //INPATIENT
}
}
独自のルールエンジンを構築するのではなく、Groovyをドメイン固有言語(DSL)として使用するオープンソースJavaルールエンジン)であるオープンソースN-CUBEエンジンを検討することをお勧めします。
RETEベースのルールエンジンのような非シーケンシャルルールエンジンとは対照的に、シーケンシャルルールエンジンです。シーケンシャルルールエンジンの利点は、ルールのデバッグが非常に簡単であることです。非常に大きなルールセットから推論を解読しようとするのは非常に困難ですが、N-CUBEのようなシーケンシャルルールエンジンを使用すると、ルールのトレースはシーケンシャル「コードロジック」に従うことに非常に似ています。
N-CUBEには、デシジョンテーブルとデシジョンツリーの両方のサポートが組み込まれています。 N-CUBE内のデシジョンテーブルとツリーを使用すると、多次元Excelのように、セル内でデータまたはコードを実行できます。 「マクロ」言語(DSL)はGroovyです。セル内にコードを記述する場合、パッケージステートメント、インポート、クラス名、または関数を定義する必要はありません。これらすべてが追加され、DSLコードスニペットの読み取り/書き込みが容易になります。
このルールエンジンは、GitHubの https://github.com/jdereg/n-cube で入手できます。
これは私がすることです。マッチングに応じて一連の正規表現変数を作成し、ビジネスロジックをコーディングします。ルールセットがこれよりも複雑になった場合、サーバー上のApache commons CommandLineParser
実装を使用します。
ただし、GUI/HTMLおよびドロップダウンとサブドロップダウンのセットを使用できます。そうすれば、データベースクエリを明確に作成できます。
Clojure には Clara と呼ばれるルールエンジンがあり、これは Javaから使用可能 およびClojure [Java] Scriptと同じです。それから使えるものを作るのはとても簡単だと思います。
TextAreaの代わりに、provideは固定状態(PATIENT_TYPE)および固定operator()の選択ボックスとして提供され、これで完了です。とにかく、Webアプリの外観を制御します。
クロージャー、つまりGroovyで単純なルールエンジンを構築できます。
def sendToOutPatient = { ... };
def sendToInPatient = { ... };
def patientRule = { PATIENT_TYPE ->
{'A': sendToOutPatient,
'B': sendToInPatient}.get(PATIENT_TYPE)
}
static main(){
(patientRule('A'))()
}
ルールをクロージャーとして定義したり、再利用/再割り当てしたり、DSLを構築したりすることもできます。
また、GroovyはJavaに簡単に組み込むことができます。例:
GroovyShell Shell = new GroovyShell(binding);
binding.setVariable("foo", "World");
System.out.println(Shell.evaluate("println 'Hello ${foo}!';));
ユーザーとよく話し合い、なぜこれを構成する必要があるのか、また、構成のどの変更が予定されているのかを尋ねます。今後の変更が特定され、可能性が高く、リモートで可能性があり、とんでもない可能性があるものを見つけます。そして、それらをどれだけ早く実装する必要があるか。変更ごとに、小さな更新リリースを書くことは受け入れられますか?
この柔軟性を念頭に置いて、独自のソリューションを完全なエンジンを組み込むオプションと比較するオプションを評価してください。各変更の実装方法を簡単に書き留めて、今後の変更シナリオに対して簡単なソリューションを「テスト」します。ありそうもないシナリオに大きなコストがかかっても大丈夫です。ただし、可能性の高いシナリオにもコストがかかる場合は、より一般的なソリューションを選択することをお勧めします。
検討するオプションについては、私はよだれとあなた自身で書く提案の両方が好きです。 3番目のオプション:毎年の法的更新を伴う財務登録パッケージを実装する場合、コードでルールを実装することは非常に成功しましたが、その設定はSQLテーブルで構成可能です。したがって、あなたの場合、それは次のような表を意味するかもしれません:
patient_type | admission_type | inpatient_or_outpatient
-------------------------------------------------------
'A' | 'O' | 'Outpatient'
'B' | NULL | 'Inpatient'
(私たちのテーブルは、ユーザーが変更をステージングできるようにする、日付の開始および終了の有効性列を持つ傾向があります)
DSLを書くことになった場合は、 http://martinfowler.com/books/dsl.html をご覧ください。いくつかのアプローチの詳細な説明があります。警告として: Q and A section Martin Fowlerは次のように書いています:
だからこれはフックです-ビジネスマンはルールを自分で書くのですか?
一般的に私はそうは思いません。ビジネスマンが独自のルールを作成できる環境を作るのは大変な作業です。快適な編集ツール、デバッグツール、テストツールなどを作成する必要があります。ビジネスマンがルールを読むことができるように十分なことをすることで、ビジネス向けDSLのメリットを最大限に活用できます。その後、彼らはそれらを正確にレビューし、それらについて開発者と話し、開発者が適切に実装するための変更案を作成することができます。 DSLをビジネスで読み取り可能にすることは、ビジネスで書き込み可能になるよりもはるかに少ない労力ですが、ほとんどの利点が得られます。 DSLをビジネスで書き込み可能にする努力をする価値がある場合もありますが、より高度な目標です。
Javaのみを使用したコードの解析は実装の自殺であるため、 JflexおよびCUP を使用して簡単なコンパイラを記述できます。これはJavaバージョンですGNU FLEX
およびYACC
。この方法で、Jflex
(トークンはIF
、ELSE
などのキーワード)を使用して単純なトークンを生成できますが、CUPはコードを実行するためにこれらのトークンを消費します。
ルールエンジンの実装はnot簡単です。意味のあるルールベースのシステムには、前方連鎖と後方連鎖の両方をサポートする推論エンジンと、幅優先および深さ優先の検索戦略があります。 Easy Rulesにはこれはありません。すべてのルールを一度だけ実行します。 Droolsは前方および後方チェーンをサポートし、afaikは深さ優先および幅優先もサポートします。 here と説明されています。
私の経験から、DroolsはJava向けの唯一の意味のあるルールエンジンです。制限があります。 5年以上前にDroolsを使用したことがあります。