简体   繁体   中英

What is a good design to execute the methods based on boolean values in Database in Java?

We have few rules, which are Implemented as methods in Java. But sometimes we need to bypass the rules. So for each rule, we have a boolean Indicator to indicate whether to execute or not. What can be a good design to map the methods to boolean values in Database and execute methods based on the boolean values.

Below is sample template

1 Rule1 true
2 Rule2 false
3 Rule3 true
4 Rule4 true

So, now I need to execute method1(), method3() and method4() respectively.

One Simple way can be using If(rulee == true) executeMethod();

Second is using a Switch to execute the cases (method calls)

Note: We may need to execute the methods in different locations(methods). So please dont consider that all the methods will be called from a single method.

Can I make use of AOP by any chance?

You could define the basic interface as

public interface Rule {
  boolean canExecute();
  void execute();
}

and convert the methods into Rule interface implementations. The boolean value in the database would map to canExecute() return value.

This would be a good idea if methods are becoming complex, there's more than a few of them and the parent class is starting to look like a God Object .

Use Java 8 Stream api and Enums.

public class Main {

    public enum Rule {
        RULE1 {
            @Override
            public void doWork() {

            }
        },
        RULE2 {
            @Override
            public void doWork() {

            }
        };

        public abstract void doWork();
    }

    public static void main(String[] args) {
        List<String> rules = new ArrayList<>();
        rules.stream()
                .map(Rule::valueOf)
                .forEach(Rule::doWork);
    }

}

You can just call all methods and do the validation part within the method implementation, eg:

void rule1(Object... args){
  if (!applyRule1){
   return;
  }
...
}

With that approach, you can reduce cyclomatic complexity and prevent tools such as PMD from complaining.

Update: I replaced Consumer interface with Runnable in my original answer, because it aligns with example in the question better.

You can try to upgrade your Rule entity, here is an idea using Runnable interface:

class Rule {

    private boolean isActive;
    private Runnable runnable;

    public Rule(boolean isActive, Runnable runnable) {
        this.isActive = isActive;
        this.runnable = runnable;
    }

    public void executeIfActive() {
        if (isActive) {
            runnable.run();
            isActive = false;
        }
    }
}

Example of the use:

public class Demo {

    public static void main(String[] args) {
        Demo demo = new Demo();
        List<Rule> rules = List.of(new Rule(true, demo::m1), new Rule(false, demo::m2));
        rules.forEach(Rule::executeIfActive);
    }

    void m1() { ... }

    void m2() { ... }
}

demo::m1 is a method reference that would invoke the method demo.m1() , and the same for m2 .

Another approach is to store the method names as strings in the database. If your database supports arrays, that's particularly easy.

Then in Java you can set up an executor that accepts a String name and execute the respective rule:

import java.util.List;
import static java.util.Arrays.asList;

public class ByNameExecutor {
  enum Rule {
    Rule1 { @Override void rule() { System.out.println("Executed rule 1"); } },
    Rule2 { @Override void rule() { System.out.println("Executed rule 2"); } },
    Rule3 { @Override void rule() { System.out.println("Executed rule 3"); } },
    Rule4 { @Override void rule() { System.out.println("Executed rule 4"); } },
    ;
    abstract void rule();
  }

  public void execute(String ruleName) {
    Rule.valueOf(ruleName).rule();
  }

  public void execute(List<String> ruleNames) {
    ruleNames.stream().forEach(this::execute);
  }

  public static void main(String [] args) {
    String [] methodList = { "Rule1", "Rule2", "Rule4" };
    new ByNameExecutor().execute(asList(methodList));
  }
}

An advantage of this approach is that you don't need to change the database schema to add a rule. Just start storing the new rule's string name. A disadvantage is that if you need to query on presence of or absence of a given rule, the database must support indexes over arrays.

Instead of store Boolean you can store method names in this field accordingly. Then all you need to do would be invoke that method using reflection.

Table:

Id RULE_NAME METHOD_NAME
1 Rule1 method1
2 Rule2 
3 Rule3 method3
4 Rule4 method4

The method can be invoked like this:

ResultSet srs = stmt.executeQuery("SELECT METHOD_NAME from table");
while (srs.next()) {
    String methodName = srs.getString("METHOD_NAME");

    if (!TextUtils.isEmpty(methodName)) {
        Class<?> c = Class.forName("class name");
        Method method = c.getDeclaredMethod(methodName, parameterTypes); // method name will be fetched from Database
        method.invoke(objectToInvokeOn, params);
    }
}

Reflection API > Invoking Methods

If I understand the problem correctly then it should work. You can have a method like below and call it from anywhere.

Or these booleans can also be a rule and you can add multiple methods in one IF condition

void executeMethods(boolean m1, boolean m2, boolean m3, boolean m4){
   if(m1) m1();
   if(m2) m2();
   if(m3) m3();
   if(m4) m4();
}

executeMethods(true,false,false,true);

Lets solve this problem with a database driven approach, and Spring AOP.

You have several hundred rules, and do not wish to pollute the current code with boilerplate code like void method1() { if (!rule1) return; .. do method } void method1() { if (!rule1) return; .. do method } or have to create additional interfaces which all rule based methods must implement.

Spring AOP provides a means to leave the current base in tact, and instead have methods intercepted (via a proxy) to determine if the method should run or not. You write the proxy code once, and the only ongoing requirement is to keep the database up to date with new rules.

Step 1 : Build a database schema which maps method names to boolean values

method_name VARCHAR(100), is_rule_active tinyint(1);

There will be one row for each rule. The row will contain the method name (as it appears in the java code) and a boolean true=active, false=not active.

Step 2: Build an interface to the database (DAO)

You need a simple abstraction to the database. Something like:

public interface RuleSelectionInterface {
    boolean isRuleActive(String methodName);
}

The implementation will be basic DAO code, which will query for the row with method_name equal to methodName . For simplicity, and to demonstrate, I used a Map instead:

@Repository
public class RuleSelectionImpl implements RuleSelectionInterface {

    Map<String, Boolean> rules;

    public RuleSelectionImpl() {
        rules = new HashMap<>();
        rules.put("rule1Method", true);
        rules.put("rule2Method", false);
    }

    @Override
    public boolean isRuleActive(String methodName) {
        if (!rules.containsKey(methodName))
            return false;
        return rules.get(methodName);
    }

}

Step 3: Create a Spring AOP aspect

An aspect is created to intercept method calls, and determine when the call should be executed.

To allow execution to be continued, or aborted, you use an @Around advice, which will be passed the execution point (by means of a ProceedingJoinPoint ) from which you can either abort (the proxy method simply returns) or run the code by using the proceed method.

There is some choice here on which methods should be intercepted (this is done by defining pointcuts). This example will intercept methods with names starting with rule :

@Around("execution(* rule*(..))") 

You could intercept all methods, or methods based on naming patterns, etc. For a detailed understanding of how to create pointcuts to intercept methods refer to Spring AOP

Here is the AOP code, which is called upon method interception, and which uses your database rule interface to look up if the rule is active for this method name:

@Aspect
@Component
public class RuleAspects {

   @Autowired
   private RuleSelectionInterface rulesSelectionService;

   @Around("execution(* rule*(..))") 
   public void ruleChooser(ProceedingJoinPoint jp) throws Throwable
   {
       Signature sig = jp.getSignature();
       System.out.println("Join point signature = "+sig);
       String methodName = sig.getName();
       if (rulesSelectionService.isRuleActive(methodName))
           jp.proceed();
       else 
          System.out.println("Method was aborted (rule is false)");
   }

}

Sample usage:

I created a simple class with two methods (however this approach works regardless of how many classes/methods you have rule based methods for).

@Component
public class MethodsForRules {

public void rule1Method() {
    System.out.println("Rule 1 method");
}

public void rule2Method() {
    System.out.println("Rule 2 method");
}
}

You will have noticed in the Map that rule1Method is set to true, and rule2Method is set to false.

When the code tries to run rule1Method and rule2Method:

MethodsForRules r;  // Is a Spring managed bean.
r.rule1Method();
r.rule2Method();

Produces the following output:

Join point signature = void com.stackoverflow.aoparound.demo.MethodsForRules.rule1Method()
Rule 1 method   <- Here is the method running
Join point signature = void 
com.stackoverflow.aoparound.demo.MethodsForRules.rule2Method()
Method was aborted (rule is false)  <- Here the method is aborted

Summary:

This demonstration has shown how Spring AOP can be used, in combination with a rules based interface, to intercept methods (by using a proxy), examine the method name which was intercepted, lookup the active status for this method, and either run the method, or abort it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM