Reputation: 741
I am trying to implement Rule engine desing pattern in my use case but not able to fit the pieces in the right place.
RuleEngine is where all rules are validated for a transaction before making it approved
public class RuleEngine {
private Predicate<Transaction> predicates;
private Transaction transaction;
public void setTransaction(Transaction transaction){
this.transaction = transaction;
}
public void addRules(Predicate<Transaction> predicates) {
this.predicates = predicates;
}
public void executeRules() {
if(predicates.test(transaction)) {
// all rules are valided - payment success
}
}
}
Below Payments class is invoked by parent where transaction and its type are provided.
Then based on transaction, rules are added which is the difficult part.
Because of transactionUtils - hard dependency required to autowired, causing predicate chaining looks very ugly and seems not the correct way.
@Component
public class Payments {
@Autowired
PredicateHelper predicateHelper;
public void process(Transaction transaction, String type) {
RuleEngine ruleEngine = new RuleEngine();
ruleEngine.setTransaction(transaction);
switch (type) {
case "card" :
ruleEngine.addRules(getCardRules());
break;
case "cash" :
ruleEngine.addRules(getCashRules());
break;
default : log.error("Invalid case");
}
ruleEngine.executeRules();
}
private Predicate<Transaction> getCardRules(){
return predicateHelper.rule1
.and(predicateHelper.rule2)
.and(predicateHelper.rule3); // Predicate chaining
}
private Predicate<Transaction> getCashRules(){
return predicateHelper.rule1
.and(predicateHelper.rule4)
.and(predicateHelper.rule5); // Predicate chaining
}
}
@Component
public class PredicateHelper {
@Autowired
TransactionUtils transactionUtils; // hard dependency - in house library
public Predicate<Transaction> rule1 = transaction -> "rule1".equals(transactionUtils.getName(transaction));
public Predicate<Transaction> rule2 = transaction -> "rule2".equals(transactionUtils.getName(transaction));
public Predicate<Transaction> rule3 = transaction -> "rule3".equals(transactionUtils.getName(transaction));
public Predicate<Transaction> rule4 = transaction -> "rule4".equals(transactionUtils.getName(transaction));
public Predicate<Transaction> rule5 = transaction -> "rule5".equals(transactionUtils.getName(transaction));
}
Is there a ways to have better predicate chaining with this solution. Thanks in advance.
Upvotes: 0
Views: 447
Reputation: 173
You could make the dependency itself as a parameter for arguments passed in api, so when required, we could inject the component while doing validation. Also, since we want to bind the transactionType along with its rules,it deserves a class/enum for itself defining the coupling though lossely.
//Component
class RuleValidator {
//Autowired
TransactionUtils transactionUtils;
boolean validate(Transaction transaction, BiPredicate<Transaction,TransactionUtils> predicate){
return predicate.test(transaction,transactionUtils);
}
}
public enum TransactionType {
CARD(RulesFactory.getDefaultCardRules()),CASH(RulesFactory.getDefaultCashRules()),NO_RULES((key1,key2)->true);
private final BiPredicate<Transaction,TransactionUtils> rulesApplied;
TransactionType(BiPredicate<Transaction, TransactionUtils> rulesApplied) {
this.rulesApplied = rulesApplied;
}
public BiPredicate<Transaction, TransactionUtils> getRulesApplied() {
return rulesApplied;
}
public BiPredicate<Transaction, TransactionUtils> addAdditionalPredicate(BiPredicate<Transaction,TransactionUtils> ... additional ) {
BiPredicate<Transaction, TransactionUtils> additionalValidation = Arrays.stream(additional)
.reduce((k, v) -> true, BiPredicate::and);
return additionalValidation.and(rulesApplied);
}
}
interface RulesFactory {
public static boolean rule1(Transaction transaction,TransactionUtils transactionUtils) {
return "rule1".equals(transactionUtils.getName(transaction));
}
public static boolean rule2(Transaction transaction,TransactionUtils transactionUtils) {
return "rule2".equals(transactionUtils.getName(transaction));
}
public static boolean rule5(Transaction transaction,TransactionUtils transactionUtils) {
return "rule5".equals(transactionUtils.getName(transaction));
}
public static BiPredicate<Transaction, TransactionUtils> getDefaultCardRules(){
BiPredicate<Transaction,TransactionUtils> andPredicate = (currentTransaction,transactionUtilsComponent) -> true;
return andPredicate.and(RulesFactory::rule1)
.and(RulesFactory::rule2);
}
public static BiPredicate<Transaction, TransactionUtils> getDefaultCashRules() {
BiPredicate<Transaction,TransactionUtils> andPredicate = (currentTransaction,transactionUtilsComponent) -> true;
return andPredicate.and(RulesFactory::rule2)
.and(RulesFactory::rule5);
}
}
class RuleEngine{
RuleValidator ruleValidator;
private Transaction transaction;
private TransactionType transactionType;
public void setTransaction(Transaction transaction){
this.transaction = transaction;
}
public void setTransactionType(TransactionType transactionType){
this.transactionType = transactionType;
}
public void executeRules() {
if(ruleValidator.validate(transaction,transactionType.getRulesApplied())) {
// all rules are valided - payment success
}
}
}
Upvotes: 0