Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Hacking 
Rule Systems & State Machines 
with Ap...

Hacking 
Rule Systems & State Machines 
with Apple's GameplayKit Framework

Don't go with the name. It has nothing to do with "Games". Solve problems which are as simple as Text Validator to as complex as Payments System which has "n" number of states and "m" number of rules to evaluate.

 Also provides better readability, scalability, and reusability.

Srikanth KV

July 28, 2019
Tweet

Other Decks in Programming

Transcript

  1. Hacking 
 Rule Systems & State Machines 
 with GameplayKit

    - Nothing to do with "Games" Srikanth K V PhonePe Private Limited
  2. What does it solve? Solve problems which is as simple

    as Text Validator to as complex as Payments System which has "n" number of states and "m" number of rules to evaluate.
 
 Also provides better readability, scalability and reusability.
  3. Introduction - Rule System (GKRuleSystem) - State Machine (GKStateMachine) -

    Combining Rule System with State Machine - Usecases
  4. Example enum PasswordError: Error { case passwordsDontMatch case notLongEnough case

    uppercaseMissing case lowerCaseMissing case noNumbers case spacesNotAllowed } enum ValidationResult { case valid case invalid(error: PasswordError) }
  5. Example enum PasswordError: Error { case passwordsDontMatch case notLongEnough case

    uppercaseMissing case lowerCaseMissing case noNumbers case spacesNotAllowed } enum ValidationResult { case valid case invalid(error: PasswordError) }
  6. Wrapper class Fact: NSObject { typealias EvaluationBlock = (_ system:

    GKRuleSystem) -> Bool let error: PasswordError let evaluationBlock: EvaluationBlock init(error: PasswordError, evaluationBlock: @escaping EvaluationBlock) { self.error = error self.evaluationBlock = evaluationBlock } }
  7. Creating Rule class FactRule: GKRule { let fact: Fact init(fact:

    Fact) { self.fact = fact super.init() } override func evaluatePredicate(in system: GKRuleSystem) -> Bool { return fact.evaluationBlock(system) } override func performAction(in system: GKRuleSystem) { system.assertFact(fact) } }
  8. class PasswordValidator { } private let lengthFact = Fact(error: .notLongEnough)

    { (system: GKRuleSystem) in guard let password = system.state["password"] as? String, password.count >= 8 else { return false } return true }
  9. private let passwordsMatchFact = Fact(error: .passwordsDontMatch) { system in guard

    let password = system.state["password"] as? String, let confirmedPass = system.state["confirmedPassword"] as? String, password == confirmedPass else { return false } return true }
  10. func validatePasswords(password: String, confirmedPassword: String) -> ValidationResult { // create

    the facts let facts = [lengthFact, uppercaseLetterFact, lowercaseLetterFact, numberFact, noWhiteSpaceFact, passwordsMatchFact] Password Validator
  11. func validatePasswords(password: String, confirmedPassword: String) -> ValidationResult { // create

    the facts let facts = [lengthFact, uppercaseLetterFact, lowercaseLetterFact, numberFact, noWhiteSpaceFact, passwordsMatchFact] /* create rule system and provide all the facts which we want to evaluate */ let ruleSystem = GKRuleSystem() ruleSystem.add(facts.map(FactRule.init)) Password Validator
  12. func validatePasswords(password: String, confirmedPassword: String) -> ValidationResult { // create

    the facts let facts = [lengthFact, uppercaseLetterFact, lowercaseLetterFact, numberFact, noWhiteSpaceFact, passwordsMatchFact] /* create rule system and provide all the facts which we want to evaluate */ let ruleSystem = GKRuleSystem() ruleSystem.add(facts.map(FactRule.init)) // add the state to the rule system ruleSystem.state["password"] = password ruleSystem.state["confirmedPassword"] = confirmedPassword // evaluate ruleSystem.evaluate() } Password Validator
  13. // extract the errors from the facts after // evaluation

    let erroredFacts = facts.filter { ruleSystem.grade(forFact: $0) == 0 } let errors = erroredFacts.map { $0.error } if !errors.isEmpty { return .invalid(errors: errors) } else { return .valid } Extracting Result
  14. // extract the errors from the facts after // evaluation

    let erroredFacts = facts.filter { ruleSystem.grade(forFact: $0) == 0 } let errors = erroredFacts.map { $0.error } if !errors.isEmpty { return .invalid(errors: errors) } else { return .valid } Extracting Result
  15. class Red: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  16. class Red: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  17. class Green: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  18. class Green: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  19. class Yellow: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Red.Type || stateClass is Green.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  20. class Yellow: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Red.Type || stateClass is Green.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  21. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  22. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  23. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  24. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  25. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Init State + Fraud Check System
  26. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Init State + Fraud Check System
  27. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Failed Init State + Fraud Check System
  28. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Failed Yes Init State + Fraud Check System
  29. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Failed Yes No Init State + Fraud Check System
  30. class InitState: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass == PendingState.Type || stateClass == FailedState.Type } override func didEnter(from previousState: GKState?) { } } Init Pending Failed Yes No
  31. override func didEnter(from previousState: GKState?) { let facts = [locationFact,

    deviceVerificationFact, amountThresholdFact, transactionRateFact] let ruleSystem = GKRuleSystem() ruleSystem.add(facts.map(FactRule.init)) ruleSystem.state["locationInfo"] = data.locationInfo ruleSystem.state["deviceInfo"] = data.deviceInfo ruleSystem.state["amountInfo"] = data.amountInfo ruleSystem.state["transactionRate"] = data.transactionRate ruleSystem.evaluate() } Amount Threshold Check Transaction Frequency Check Device
 Check Location
 Check Fraud Check System
  32. let erroredFraudFacts = facts.filter { ruleSystem.grade(forFact: $0) == 0 }

    if erroredFraudFacts.count > 0 { stateMachine?.enter(FailedState.self) } else { stateMachine?.enter(PendingState.self) } Reading the Result
  33. - Offer Rule System - Check for offer window -

    Minimum Amount Check - Other set of rules - Order Completion - Book a cab - Order food Other Usecases
  34. Advantages - Adding new rule - Adding new state -

    Reusing the same rule in multiple usecases/ states - App transition is always under control.