なぜルールエンジンが必要なのか?
コードを書く前に、まずルールエンジンがなぜ必要なのかを考えてみましょう:
- ビジネスロジックをアプリケーションのコアコードから分離する
- 技術的な知識がない人でも、完全なデプロイなしでルールを調整できる
- システムを変化に対応しやすくする(変化は必ず訪れます)
- 保守性とテストのしやすさを向上させる
これで皆さんも同じページに立ったので、実際に手を動かしてみましょう!
コアコンポーネント
私たちのルールエンジンは、次の3つの主要な部分で構成されます:
- ルール:個々のビジネスルール
- ルールエンジン:ルールを処理する頭脳
- ファクト:ルールが操作するデータ
これらを一つずつ見ていきましょう。
1. ルールインターフェース
まずは、ルールのためのインターフェースが必要です:
public interface Rule {
boolean evaluate(Fact fact);
void execute(Fact fact);
}
シンプルですね?各ルールは、ファクトに対して自分自身を評価する方法と、一致した場合に何をするかを知っています。
2. ファクトクラス
次に、ファクトクラスを定義しましょう:
public class Fact {
private Map attributes = new HashMap<>();
public void setAttribute(String name, Object value) {
attributes.put(name, value);
}
public Object getAttribute(String name) {
return attributes.get(name);
}
}
この柔軟な構造により、ファクトにあらゆる種類のデータを追加できます。ビジネスデータのためのキーと値のストアと考えてください。
3. ルールエンジンクラス
さて、メインのルールエンジンです:
public class RuleEngine {
private List rules = new ArrayList<>();
public void addRule(Rule rule) {
rules.add(rule);
}
public void process(Fact fact) {
for (Rule rule : rules) {
if (rule.evaluate(fact)) {
rule.execute(fact);
}
}
}
}
ここで魔法が起こります。エンジンはすべてのルールを反復し、必要に応じて評価と実行を行います。
すべてをまとめる
コアコンポーネントが揃ったので、簡単な例でそれらがどのように連携するかを見てみましょう。例えば、eコマースプラットフォームの割引システムを構築しているとします。
public class DiscountRule implements Rule {
@Override
public boolean evaluate(Fact fact) {
Integer totalPurchases = (Integer) fact.getAttribute("totalPurchases");
return totalPurchases != null && totalPurchases > 1000;
}
@Override
public void execute(Fact fact) {
fact.setAttribute("discount", 10);
}
}
// 使用例
RuleEngine engine = new RuleEngine();
engine.addRule(new DiscountRule());
Fact customerFact = new Fact();
customerFact.setAttribute("totalPurchases", 1500);
engine.process(customerFact);
System.out.println("Discount: " + customerFact.getAttribute("discount") + "%");
この例では、顧客が1000ドル以上の購入をした場合、10%の割引を受けます。シンプルで効果的、そして簡単に変更可能です。
さらに進める
基本を押さえたところで、ルールエンジンを強化する方法をいくつか探ってみましょう:
1. ルールの優先順位
ルールに優先順位フィールドを追加し、エンジン内でソートします:
public interface Rule {
int getPriority();
// ... 他のメソッド
}
public class RuleEngine {
public void addRule(Rule rule) {
rules.add(rule);
rules.sort(Comparator.comparingInt(Rule::getPriority).reversed());
}
// ... 他のメソッド
}
2. ルールの連鎖
ルールが他のルールをトリガーできるようにします:
public class RuleEngine {
public void process(Fact fact) {
boolean ruleExecuted;
do {
ruleExecuted = false;
for (Rule rule : rules) {
if (rule.evaluate(fact)) {
rule.execute(fact);
ruleExecuted = true;
}
}
} while (ruleExecuted);
}
}
3. ルールグループ
ルールをグループ化して管理を改善します:
public class RuleGroup implements Rule {
private List rules = new ArrayList<>();
public void addRule(Rule rule) {
rules.add(rule);
}
@Override
public boolean evaluate(Fact fact) {
return rules.stream().anyMatch(rule -> rule.evaluate(fact));
}
@Override
public void execute(Fact fact) {
rules.forEach(rule -> {
if (rule.evaluate(fact)) {
rule.execute(fact);
}
});
}
}
パフォーマンスの考慮事項
私たちのルールエンジンはかなりスムーズですが、最適化の余地は常にあります:
- ルールの保存により効率的なデータ構造を使用する(例:階層的なルールのためのツリー)
- 頻繁にアクセスされるファクトやルール評価のキャッシュを実装する
- 大規模なルールセットのために並列処理を検討する
保守性のヒント
ルールエンジンが手に負えなくなるのを防ぐために:
- 各ルールを詳細に文書化する
- ルールのバージョン管理を実装する
- 技術的でないユーザーがルールを変更できるユーザーフレンドリーなインターフェースを作成する
- 定期的に古いルールを監査し、クリーンアップする
まとめ
以上で、約150行のコードでスリムで強力なルールエンジンが完成しました。柔軟で拡張可能で、次のビジネスロジックの危機からあなたを救うかもしれません。良いルールエンジンの鍵は、必要に応じて複雑さを許容しつつ、シンプルに保つことです。
最後に考えてみてください:このルールエンジンのコンセプトをコードベースの他の領域にどのように適用できるでしょうか?現在のプロジェクトで複雑な意思決定プロセスを簡素化できるでしょうか?
コーディングを楽しんで、ビジネスロジックが常にダイナミックで素晴らしいものでありますように!
「シンプルさは究極の洗練である。」 - レオナルド・ダ・ヴィンチ(彼はコーディングについて話していたわけではありませんが、そうだったかもしれません)
P.S. ルールエンジンについてもっと深く知りたい方は、Easy Rules や Drools などのオープンソースプロジェクトをチェックしてみてください。自作のエンジンを次のレベルに引き上げるためのアイデアが得られるかもしれません!