them's the rules - using a rules engine to wrangle complexity
TRANSCRIPT
Them’s the Rules
byMicah Breedlove
Using a Rules Engine to Wrangle Complexity
PHP Dev since ‘98Symfony Evangelist
@druid628http://micah.breedlove.in/
Sr. Developer / Team Lead @ iostudio
Who is this guy?
Rules EnginesDrools
Open Rules
Jess
Ruler (PHP)
Ruler Written by Justin Hileman(BobTheCow)
GitHub: https://github.com/bobthecow/Ruler
Packagist: ruler/ruler
Some definitions
Conditional Logic“hypothetical logic (of a proposition) consisting of two component propositions... so that the proposition is false only when the antecedent is true and the consequent false.”
How we handle the unknown.
Rules Engines“A business rules engine is a software system that executes one or more business rules in a runtime production environment.”
“An added layer of complexity to carry out, often complicated, business logic of the application”
in other words...
Business Logic?
Company policies
Legal Regulations
ExamplesDiscounts on products purchased
Combination of productsSubscribed services
Additional FeesGeographical assessmentRisk assessment
What is wrong with this?if ( $obj->getName() == ‘abc’) { } elseif ($obj->getName() == ‘def’) {} elseif ($obj->getName() == ‘ghi’) { } elseif ($obj->getName() == ‘jkl’ ) {} elseif ($obj->getName() == ‘mno’ ) {} elseif ($obj->getName() == ‘pqr’ && $obj->getClient()->getCategory() !== ‘hotChkn’) {}elseif ($obj->getName() == ‘Hg(CNO)2’ && $obj->getClientKey() == ‘tuco’){}elseif ($obj->getName() == ‘blueSky’ && $obj->getClientKey() == ‘losPollosHermanos’) {} elseif ($obj->getName() == ‘blueSky’ && $obj->getClientKey() == ‘heisenberg’){}
...
Should be kept trimmed, maintained and under control
Or else...
Pandemonium Ensues
Conditional statements are like Kudzu
Fixable?Something to …
Abstractly handle multiple conditionsWithMinimal conditional logicand isReusable
Simplifies a block into a generic block
Support more than a simple/singular condition
Abstraction
Minimize Conditional LogicDrop the ever-building conditionals
Replace with smaller defined block
ReusabilityAbility to support all existing conditions
Individual rules can be evaluated in other RuleSets
The saga begins… if ($bond->getClient()== ‘client1’ && $event->getName() == ‘bond.create’) {
$this->calculateClient1BondAdjustments($bond);} elseif ($bond->getClient() == ‘client1’ && $event->getName() == ‘payment.made’) {
$this->calculateClient1Payment($bond, $payment);} elseif ($bond->getClient() == ‘client2’ && $event->getName() == ‘bond.create’) {
$this->calculateClient2BondAdjustments($bond);} elseif ($bond->getClient() == ‘client3’ && $event->getName() == ‘bond.create’) {
$this->calculateClient3BondAdjustments($bond);} ...
What We NeedIf a Bond is made in Collegedale
A $25 fee should be assessed
$rule = $ruleFactory->getBondRules();$clientRule = [
‘district’ => ‘collegedale’, ‘district_fee’ => ‘25’,‘fee_adjustment’ => ‘+’ ];
$context = new Context([ ‘bond_district’ => $bond->getDistrict(),‘district’ => $clientRule[‘district’],
‘district_fee’ => $clientRule[‘district_fee’],‘fee_adjustment’ => $clientRule[‘’fee_adjustment’] ]);
$rule->execute($context);
Rule Elements
Ruler - Elements of the Equation3 Parts
Context
Rule
Execution/Evaluation
Context - All Applicable Data
Variable Data:‘bond_district’ => ‘collegedale’,
Control Data:‘district’ => ‘collegedale’, ‘district_fee’ => ‘25’,‘fee_adjustment’ => ‘+’
Variable Data Request User Specific
(includes session)
Control Data Specific to the Rule
Rule
$rule = $rb->create( $rb['bond_district']->EqualTo($rb['district'])
);
How the Context Stacks UpHow to evaluate the dataWhat to evaluate
Variable Data:‘bond_district’ => ‘collegedale’,
Control Data:‘district’ => ‘collegedale’, ‘district_fee’ => ‘25’,‘fee_adjustment’ => ‘+’
Rule (cont.)Compound Rules
$rule = $rb->create( $rb->LogicalAnd($rb['bond_district']->EqualTo($rb['district']),$rb['bond_amount']->GreaterThan('20000')) );
Evaluation/ExecutionEvaluate - (boolean) Did it meet the condition?
Execution - (void) If it’s true do something
Evaluation Example
Rule:$rule = $rb->create(
$rb['bond_district']->EqualTo($rb['district']) );
$status = $rule->evaluate($context);echo $status; // true
Control Data:‘district’ => ‘collegedale’, ‘district_fee’ => ‘25’,‘fee_adjustment’ => ‘+’
Variable Data:‘bond_district’ => ‘collegedale’
Execution Example
$rule->execute($context);// output: An additional mileage fee will be charged.
Control Data:‘district’ => ‘collegedale’, ‘district_fee’ => ‘25’,‘fee_adjustment’ => ‘+’
Variable Data:‘bond_district’ => ‘collegedale’
Rule:$rule = $rb->create(
$rb['bond_district']->EqualTo($rb['district']),function() {
echo 'An additional mileage fee will be charged.';} );
if ($bond->getClient()== ‘client1’ && $event->getName() == ‘bond.create’) {$this->calculateClient1BondAdjustments($bond);
} elseif ($bond->getClient() == ‘client1’ && $event->getName() == ‘payment.made’) {$this->calculateClient1Payment($bond, $payment);
} elseif ($bond->getClient() == ‘client2’ && $event->getName() == ‘bond.create’) {$this->calculateClient2BondAdjustments($bond);
} elseif ($bond->getClient() == ‘client3’ && $event->getName() == ‘bond.create’) {$this->calculateClient3BondAdjustments($bond);
} ...
Finally this…
...becomes this
$ruleObject = $rulesRepo->get($event->getName()); // assume bond.create$context = $ruleObject->getContext();$context[‘bond’] = $bond;$rule = $ruleObject->getRule();$rule->execute($context); // one rule to rule them all
Questions?
Demo Time
Thanks!
Slides: http://sthen.es/mqh4ub20
citationsJesse (Breaking Bad) - http://replygif.net/i/1323.gifKudzu - http://2.bp.blogspot.com/_RTswKzWDHeM/SE198zJTgzI/AAAAAAAAA80/ZKj4haQbiSE/s320/kudzu+house.jpgMC Hammer - http://24.media.tumblr.com/26d1484f43ff55401f8ead9d03432bb9/tumblr_moctg0LbmC1sn8pc2o1_400.gif