프론트 엔드에서 규칙 엔진의 적용에 대해 이야기하기

-2021- 복사 copy.jpg

1. 소개

      규칙 엔진은 특정 볼륨의 제품에서 독립적인 서비스로 실행되는 경우가 많으며 유효한 데이터를 수신하여 합리적인 비즈니스 의사 결정을 내립니다. 프론트 엔드 프로젝트의 지속적인 반복 하에서, 오랜 시간, 적은 주석 및 읽기 어려움과 같은 객관적인 문제로 인해 일부 중요하거나 자주 확장되는 비즈니스 모듈의 유산은 이후 반복에서 특정 문제를 야기하고 불가피하게 추가 테스트를 야기할 것입니다. 압력. 따라서 브라우저 측에서 실행할 수 있는 경량 규칙 엔진은 이러한 문제의 존재를 완전히 제거합니다.

2. 규칙 엔진의 예비 연구

      오픈소스 커뮤니티에 브라우저에 맞는 룰엔진이 구현되어 있으니, json-rules-engine오픈소스 프로젝트를 통해 룰엔진의 세계로 들어가 보겠습니다~

json-rules-engine은 강력하고 가벼운 규칙 엔진입니다. 규칙은 간단한 json 구조로 구성되어 사람이 읽을 수 있고 유지하기 쉽습니다.

json-rules-engine규칙은 JSON 데이터 형식으로 결합되어 규칙의 읽기와 규칙의 영구 저장 모두에 편리하며 가벼운 무게도 특징 중 하나입니다~

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 규칙 추가:

event규칙이 적중될 때 트리거되는 정보는       객체 conditions에 의해 정의되고 특정 규칙은 객체에 의해 정의됩니다. 각 규칙에는 팩트 이름, 연산자 및 임계값을 각각 정의하는 fact, operator및 세 부분이 포함됩니다. value또는 현재 규칙, 규칙 및 규칙의 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 规则示意图:

규칙이 많을수록 순수 코드 읽기도 더 힘들어지므로 이 때 시각적 도구를 사용하여 그림을 읽을 수 있습니다.다음 그림은 위의 경우 규칙에 대한 설명입니다.
규칙1-1.png규칙1-2.png규칙1-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