14

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?

omkar sirra
  • 696
  • 10
  • 28

8 Answers8

12

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.

Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
6

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);
    }

}
4

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

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

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

Javier Z.
  • 784
  • 4
  • 10
  • Hi Javier. Thanks for the suggestion. But this is already what I have thought, which even I have mentioned in the description. But still thinking of any better solution. – omkar sirra Apr 13 '18 at 18:53
3

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.

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
3

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.

Gene
  • 46,253
  • 4
  • 58
  • 96
2

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

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
Pelin
  • 936
  • 5
  • 12
  • Thanks for the answer. How does this help, when I have execute the methods in multiple locations. Moreover, reflection takes away the readability and control will loose in this way. And how this approach helps in executing only those Rules(methods) since there is no boolean value associated with it. Above approach forces me to execute all the methods, If I have to control with boolean value, again the same If scenario comes into picture. – omkar sirra Apr 17 '18 at 13:34
  • Class> c = Class.forName("class name"); -> If you want to execute the methods in multiple locations, you can define class name here accordingly, you can even store method location in Database. About the the true/false issue, you can check string lengths for the method_name column and if the method name length is not zero you can consider it as true, if it is empty then it is false. – Pelin Apr 17 '18 at 13:42
  • So again It falls under IF approach. – omkar sirra Apr 18 '18 at 05:02
  • Sorry I can not find a solution without "if (!TextUtils.isEmpty(methodName))" statement, using reflection would make your code clear and you can designate the method you will use in database. I thought It is worth to mention. Good luck! – Pelin Apr 18 '18 at 06:53
2

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);
UsamaAmjad
  • 4,175
  • 3
  • 28
  • 35
2

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 } 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.

Ian Mc
  • 5,656
  • 4
  • 18
  • 25