フロントエンドでのルールエンジンの適用について話す

-2021- copy copy.jpg

1 はじめに

      ルール エンジンは、多くの場合、特定のボリュームの製品で独立したサービスとして実行され、有効なデータを受け取ることによって合理的なビジネス上の決定を下します。フロントエンド プロジェクトの長年のイテレーションでは、長い時間、注釈が少ない、読みにくいなどの客観的な問題により、いくつかの重要な、または頻繁に拡張されるビジネス モジュールの遺産が、後のイテレーションで特定の問題を引き起こし、必然的に追加のテストが発生します。 .圧力。したがって、ブラウザー側で実行できる軽量のルール エンジンを使用すると、このような問題の存在を完全に排除できます。

2. ルールエンジンの予備調査

      ブラウザに適したルールエンジンがオープンソースコミュニティに実装されました.ここjson-rules-engineからオープンソースプロジェクトを通じてルールエンジンの世界に入ります~

json-rules-engine は、強力で軽量なルール エンジンです。ルールは単純な json 構造で構成されているため、人間が読みやすく、簡単に保持できます。

json-rules-engineルールは、ルールの読み取りとルールの永続的な保存の両方に便利なJSONデータ形式で結合され、軽量もその特徴の1つです〜

2.1 ゲームのルールを作る:

スポーツイベントのルールを策定する際には、次のことが合意されています。

  1. ゲームが40分に達したとき、ランナーが5つ以上のファウルを犯した場合、ランナーは退場します。
  2. ゲームが 48 分に達したとき、アスリートが 6 つ以上のファウルを犯した場合、そのアスリートは退場となります。

2.2 ルール エンジンの構築:

2.2.1 エンジンの作成:

エンジンの作成は、エンジン オブジェクトをモジュールとしてインポートしてインスタンス化するだけで完了します。

let { Engine } = require('json-rules-engine');
let engine = new Engine();
复制代码

2.2.2 ルールの追加:

eventThe information that is generated when the rule is hit is defined by the       object conditions, and the specific rule is defined by the object. 各ルールには少なくとも3 つの部分が含まれます: fact、 、operatorおよびvalue3 つの部分で、それぞれファクト名、演算子、およびしきい値を定義します。または現在のルールの範囲、ルールとanyルール論理 OR を表すために使用allし、論理 AND 関係を表すために使用します。これは、このイベントの特定のルールを構成します。

let event = {
  type: 'fouledOut',
  params: {
    message: 'Player has fouled out!'
  }
};
let conditions = {
  any: [
        {
            all: [
                { fact: 'gameDuration', operator: 'equal', value: 40 },
                { fact: 'personalFoulCount', operator: 'greaterThanInclusive', value: 5 },
            ]
        }, {
            all: [
                { fact: 'gameDuration', operator: 'equal', value: 48 },
                { fact: 'personalFoulCount', operator: 'greaterThanInclusive', value: 6 },
            ]
        }
    ]
};
engine.addRule({ conditions, event });
复制代码

下图是这个赛事规则的可视化规则示意图,在文末将会讲解如何利用可视化工具来高效阅读所定义的规则~
画像.png

2.2.3 定义Facts:

这里使用对象来表示一个Fact,让它进入到规则引擎并经过每一条规则来判断是否允许通过:

let facts = {
    personalFoulCount: 6,
    gameDuration: 40,
}
复制代码

2.2.4 运行引擎:

通过引擎校验后将在控制台输出Player has fouled out!的信息,说明这个运动员已经命中了被罚出场的规则~

const { events } = await engine.run(facts);
events.map(event => console.log(event.params.message));
复制代码

3. 线上项目分析应用

3.1 场景 1&多属性控制菜单:

      在线上项目的某一个菜单处出现了同时由 8 个属性共同控制一个菜单的显示与隐藏,猜想在最初也只是仅包含一个用户权限和数据状态的控制吧,但随着业务的持续变动导致这块的控制属性持续增加,也是在最近的迭代中又为这个菜单补充了一个属性的控制,其实这些属性中也不全是 Boolean 类型的判断,下面通过对比源代码和应用规则引擎后的代码来看一下:

/**
  	源代码:
   
    // item.error_type !== 53
    // !item.not_foreign_nationality_settle &&
    // !item.not_hongkong_macao_taiwan_settle && 
    // !item.is_excess_single && 
    // isSuperAdmin!= 'groupmanager' && 
    // !isMissingPayMode(item) && 
    // (item.status === 0 || item.status === 4 || item.status === 9) && 
    // showCreateBillBtn(item) && // 忽略,因函数逻辑不易拆解
    // !item.bankerr"
 */

// 引擎规则:
let conditions = {
    all: [
        // 非此状态的允许通过
        { fact: 'error_type', operator: 'notEqual', value: 53 },
        // 非组织管理员允许通过
        { fact: 'isSuperAdmin', operator: 'notEqual', value: 'groupmanager' },
        // 状态符合:0 4 9 允许通过
        { fact: 'status', operator: 'in', value: [0, 4, 9] },
        // 非外籍人员允许通过
        { fact: 'isForeignNationality', operator: 'equal', value: false },
        // 非港澳台人员允许通过
        { fact: 'isHongkongMacaoTaiwanRegion', operator: 'equal', value: false },
        // 未超出单笔限额允许通过
        { fact: 'isExcessSingle', operator: 'equal', value: false },
        // 支付方式未丢失用户允许通过
        { fact: 'isMissingPayMode', operator: 'equal', value: false },
        // 银行卡号未丢失时允许通过(当支付方式为银行卡且卡号丢失时此节点为true)
        { fact: 'isMissingBankCardNumber', operator: 'equal', value: false },
    ]
}

let event = {
    type: 'showAllowSettlement',
    params: {
        message: 'You can display the settlement menu.!'
    }
}
engine.addRule({ conditions, event });
复制代码

下图是上述规则的可视化示意图,通过图所示,当着 8 个属性均符合条件后才允许通过,这个时候对应的菜单才允许被显示:
画像.png

3.2 场景 2&多属性多分支控制菜单:

上面的场景 1 你可能看不出来规则引擎带来的便利,可能觉得原来的代码看起来也说的过去,那接着看这个包含多个分支的控制案例。
商户开票的功能设计由于不同的开票方式等其他的一系列因素导致这块有 4 个比较大的分支处理,这里我拆分出其中的一个分支来利用规则引擎简单的实现一下:

3.2.1 源项目逻辑分析:

下面是摘自源项目的部分逻辑控制,其中部分的属性和函数背后还有很多的逻辑处理,对于代码的阅读和功能的测试都会造成困扰:

申请开票方式1:
控制菜单显示:
  !permissionSwitch() 
  && ((isSuperAdmin && projectListCode && is_allow_apply_invoice) 
  || (projectListCode == '' && is_allow_apply_invoice))
控制菜单触发:
  (settlement_business_scenario_switch 
      && invoiceDealObj.isShowHistory 
      && this.invoiceDealObj.historyCanInvoice !== 0) 
  || (invoiceDealObj.isShowHistory 
      && invoiceDealObj.merchantHistoryCanInvoice !== 0)
复制代码

3.2.2 引擎规则编写:

通过源代码分析控制此菜单的数据达 10 个,通过逻辑与和逻辑或共同控制着 16 条规则的运行:

let conditions = {
    all: [
        { fact: 'invoice_way', operator: 'notEqual', value: 1 },
        {
            any: [
                {
                    all: [
                        {
                            any: [
                                { fact: 'isSuperAdmin', operator: 'equal', value: 'superadmin' },
                                { fact: 'isSuperAdmin', operator: 'equal', value: 'groupmanager' },
                                {
                                    all: [
                                        { fact: 'isSuperAdmin', operator: 'equal', value: 'projectmanager' },
                                        { fact: 'project_invoice_switch', operator: 'equal', value: true },
                                    ]
                                },
                                {
                                    all: [
                                        { fact: 'isSuperAdmin', operator: 'equal', value: 'financial' },
                                        { fact: 'hasCreatePower', operator: 'equal', value: 1 },
                                    ]
                                },
                            ]
                        },
                        { fact: 'projectListCode', operator: 'notEqual', value: '' },
                        { fact: 'is_allow_apply_invoice', operator: 'equal', value: true },
                    ]
                },
                {
                    all: [
                        { fact: 'projectListCode', operator: 'equal', value: '' },
                        { fact: 'is_allow_apply_invoice', operator: 'equal', value: true },
                    ]
                },
            ]
        },
        {
            any: [
                {
                    all: [
                        { fact: 'settlement_business_scenario_switch', operator: 'equal', value: true },
                        { fact: 'isShowHistory', operator: 'equal', value: true, },
                        { fact: 'historyCanInvoice', operator: 'notEqual', value: 0, },
                    ]
                },
                {
                    all: [
                        { fact: 'isShowHistory', operator: 'equal', value: true },
                        { fact: 'merchantHistoryCanInvoice', operator: 'notEqual', value: 0 }
                    ]
                }
            ]
        }
    ]
}

let event = {
    type: 'allowInvoicing',
    params: {
        message: 'Invoicing method 1 allowed!'
    }
}
复制代码

3.2.3 规则示意图:

ルールが増えると、純粋なコードを読むのも面倒になるので、このときはビジュアル ツールを使用して画像を読み進めることができます。
rule1-1.pngrule1-2.pngrule1-3.png

4. ルールの可視化スキーム

ルール可視化の前提は、書かれたエンジンルールをJSONオブジェクトに変換し、JSONオブジェクトを読み込んでルールの可視化を実現することです。

4.1 ルールの JSON 処理:

json-rules-engineモジュール内のRuleオブジェクトはtoJSON()関数を提供し、JSON 化されたルール データを取得するスクリプトを直接記述します。

const { Rule } = require("json-rules-engine");
const json = new Rule({
  conditions: {
    any: [
      {
        all: [
          { fact: "gameDuration", operator: "equal", value: 40 },
          {
            fact: "personalFoulCount",
            operator: "greaterThanInclusive",
            value: 5,
          },
        ],
      },
      {
        all: [
          { fact: "gameDuration", operator: "equal", value: 48 },
          {
            fact: "personalFoulCount",
            operator: "greaterThanInclusive",
            value: 6,
          },
        ],
      },
    ],
  },
  event: {
    type: "fouledOut",
    params: {
      message: "Player has fouled out!",
    },
  },
}).toJSON();
console.log(json);
复制代码

ルールデータを取得したら、データの簡単な処理を行う必要があります。nameルールの名前を付けて区別し、decisions取得したルールデータを次の場所に挿入します。

{
  "name": "rule",
  "decisions": [
    {
      "conditions": {
        "priority": 1,
        "any": [
          {
            "priority": 1,
            "all": [
              {
                "operator": "equal",
                "value": 40,
                "fact": "gameDuration"
              },
              {
                "operator": "greaterThanInclusive",
                "value": 5,
                "fact": "personalFoulCount"
              }
            ]
          },
          {
            "priority": 1,
            "all": [
              {
                "operator": "equal",
                "value": 48,
                "fact": "gameDuration"
              },
              {
                "operator": "greaterThanInclusive",
                "value": 6,
                "fact": "personalFoulCount"
              }
            ]
          }
        ]
      },
      "priority": 1,
      "event": {
        "type": "fouledOut",
        "params": {
          "message": "Player has fouled out!"
        }
      }
    }
  ]
}
复制代码

4.2 ルールの視覚化:

      json-rule-editorオープン ソース プロジェクトは、エンジン ルールの視覚化をすばやく完了することができます. json-rule-editorは、プロジェクト展開のオンライン ページです. ルール フォルダーを選択し、ルール ファイルをアップロードして更新するとrule、右側のタブにDecisions次の視覚化が表示されます。ルールを選択した後.ルール:
画像.png

5. まとめ

      オープンソースのルールエンジンを利用json-rules-engineすることで、複雑なロジックでの最適化処理を実現でき、視覚的なソリューションにより、エンジンルールの読み取りがより便利になります。上記のケースはjson-rules-engine浅いアプリケーションのみであり、より複雑なエンジン機能は後で調査する必要があります. 興味がある場合は、一緒に働きましょう~

おすすめ

転載: juejin.im/post/7158267715789488164