preface

When I was writing this article, I was still working on a component called rule editor. At that time, I needed to use the rule engine and formulate a protocol, so I searched for some schemes in the industry and found JSON-Logic. In view of this research, I found an optimization point for writing common codes.

What is a rules engine

A rule engine, developed from an inference engine, is a component embedded in an application that enables business decisions to be separated from application code and written using predefined semantic modules. Accept data input, interpret business rules, and make business decisions based on business rules. Baidu Encyclopedia

In fact, generally speaking, what a rule engine does is: input a piece of data to match the defined rules, and then make some behavioral decisions. Behavioral decisions here can be understood as the execution of a function.

Too many if statements

Now that we know what a rule engine is, how does this rule engine relate to our if statement? Let’s look at some code:

// Processing of loan application operation
function check() {
  // Enter the correct user name
  if (this.checkUsername(this.username)) {
    // Enter the correct id number
    if (this.checkIdCard(this.idCard)) {
      // Please enter the correct phone number
      if (this.checkTel(this.tel)) {
        // The guarantor is myself
        if (this.dbrIsSelf) {
         	// Commit the operation
		  	submit();
		  	// The guarantor is not myself
        } else {
		   // Check whether the user name of the guarantor is correct
          if (this.checkUsername(this.dbrName)) {
			 // Enter the correct id number of the guarantor
            if (this.checkIdCard(this.dbrIdCard)) {
			   // Enter the correct phone number of the guarantor
              if (this.checkTel(this.dbrTel)) {
				// Commit the operation
               	submit();
              }
            }
          }
        }
      }
    }
  }
}
Copy the code

Is it a bit of a headache to see if statements nested layer by layer above? It’s dazzling, isn’t it? This code is less readable and maintainable because we have to remember several branches of logic each time we maintain it so that we know exactly when to get that result.

In fact, there are a lot of nested optimizations for if statements, so you can search for them yourself, but I haven’t seen a rule engine solution yet. The main character of this article is the rule engine, so I will use the rule engine to solve the problem of if statement nesting.

Make a simple version of the rules engine

Now that we’ve seen what a rules engine is and what it’s going to solve, let’s start making a simple version of it. We know that for a rule engine to work, it needs a rule protocol to parse a rule and then match the corresponding input data.

So here we use JSON-Logic as the protocol for our rules engine and the rule parsing tool.

The next step is to create our “rule engine”, which is actually quite simple, adding a rule matching function to the JSON-logic to run the decision behavior. Json-logic can be found on GitHub: click here

First, use NPM to download the jSON-logic package: NPM I json-logic-js –save.

Next, create our rules engine class:

import { merge } from 'lodash';
import jsonLogic, { RulesLogic } from 'json-logic-js';

interface Rules {
  [ruleName: string] :any;
}

export class RuleEngine {
  // Store a set of rules
  private rules: Rules = {};
  // The status of the rule after the match
  private state = 'pending';
  // Error message after rule matching failed
  private errMsg = ' ';

  constructor(rules: Rules) {
    this.rules = rules;
  }

  add(rules: Rules) {
    merge(this.rules, rules);
  }

  run(ruleName: string, data: any) {
    if (this.rules[ruleName]) {
	  // The rule was successfully matched
      if (jsonLogic.apply(this.rules[ruleName], data)) {
        this.state = 'resolved';
        return this;
      } else {
        this.state = 'rejected';
        this.errMsg = 'Rules do not match';
        return this; }}else {
      this.state = 'rejected';
      this.errMsg = 'This rule was not found';
      return this; }}// Execute function after successful match
  then(cb: any) {
    this.state === 'resolved' && cb();
    return this;
  }

  // Execute the function after the match fails
  catch(errCb: any) {
    this.state === 'rejected' && errCb(this.errMsg);
    return this; }}Copy the code

We look at the start of the next if nested example, is actually two situations will pass will submit: a guarantor when I input the correct personal information can pass and guarantees for others when input the correct personal information and security information can get through it.

Ok, analysis complete, start operation! First create a data input object:

// The guarantor is myself
const obj1 = {
  username: 'Eric'.idCard: '123456789'.tel: '0123456789'.dbrIsSelf: true.dbrName: ' '.dbrIdCard: ' '.dbrTel: ' '
};

// The guarantor is another person
const obj2 = {
  username: 'Eric'.idCard: '123456789'.tel: '0123456789'.dbrIsSelf: false.dbrName: 'First'.dbrIdCard: '23456789'.dbrTel: '023456789'
};
Copy the code

Create a rule that can pass both cases according to the jSON-logic rule protocol:

Here is a brief introduction to the meaning of the following rule. The specific format of the rule can be viewed at jSON-Logic GitHub.

	username == 'Eric' && idCard == '123456789' && tel == '0123456789' && 
	(
	  dbrIsSelf == true 
	   	|| 
	  (dbrIsSelf == false && dbrName == 'First' && dbrIdCard == '23456789' && dbrTel == '023456789'))Copy the code
const rules = {
  submit: {
	and: [{'= =': [{ var: 'username' }, 'Eric'] {},'= =': [{ var: 'idCard' }, '123456789'] {},'= =': [{ var: 'tel' }, '0123456789'] {},or: [{'= =': [{ var: 'dbrIsSelf' }, true] {},and: [{'= =': [{ var: 'dbrIsSelf' }, false] {},'= =': [{ var: 'dbrName' }, 'First'] {},'= =': [{ var: 'dbrIdCard' }, '23456789'] {},'= =': [{ var: 'dbrTel' }, '023456789'}]}]}}Copy the code

The “==” of this rule is an operator in the protocol of the rule, and not just some operators. Json-logic also supports custom operators that operate on the corresponding logic through functions. The implementation details can be viewed here. (PS: If you can’t open VPN, open VPN first)

Well, now everything is ready to go

Create an instance of RuleEngine:

const ruleEngine = new RuleEngine(rules);

ruleEngine
  .run('submit', obj1)
  .then(() = > {
    console.log(`submit`);
  })
  .catch((errMsg: string) = > console.log(errMsg));
// log -> submit

ruleEngine
  .run('submit', obj2)
  .then(() = > {
    console.log(`submit`);
  })
  .catch((errMsg: string) = > console.log(errMsg));
// log -> submit
Copy the code

You will notice that we created only one rule above, but with this rule, both submit cases are taken into account. We only need to modify the rules or add rules if there is still the case of Submit. You might say, “Well, I was just writing if statements, but now I have to write rule conditions in the rule protocol. “. Yes, it’s not obvious at the moment, but when the logic of your if processing becomes too complex, you will find that you write more and more if nesting so that you can’t read your code during subsequent iterations. Simple if nesting combined with some array handlers is enough for simple judgment logic, so the purpose of this article is for those cases where the if statement is too deeply nested and the judgment logic is too complex.