Slide 1

Slide 1 text

Hacking 
 Rule Systems & State Machines 
 with GameplayKit - Nothing to do with "Games" Srikanth K V PhonePe Private Limited

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

Introduction - Rule System (GKRuleSystem) - State Machine (GKStateMachine) - Combining Rule System with State Machine - Usecases

Slide 4

Slide 4 text

GKRuleSystem

Slide 5

Slide 5 text

GKRuleSystem - Rules (GKRule)

Slide 6

Slide 6 text

GKRuleSystem - Rules (GKRule) - Input data (Dictionary)

Slide 7

Slide 7 text

GKRuleSystem - Rules (GKRule) - Input data (Dictionary) - Fact (Conclusion)

Slide 8

Slide 8 text

GKRuleSystem - Rules (GKRule) - Input data (Dictionary) - Fact (Conclusion) - Evaluate

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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 }

Slide 14

Slide 14 text

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 }

Slide 15

Slide 15 text

Password Validator

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

// 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

Slide 20

Slide 20 text

// 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

Slide 21

Slide 21 text

GKStateMachine

Slide 22

Slide 22 text

GKStateMachine - States (GKState)

Slide 23

Slide 23 text

GKStateMachine - States (GKState) - Life Cycle

Slide 24

Slide 24 text

State Machine Example - Red - Yellow - Green

Slide 25

Slide 25 text

State Machine Example - Red - Yellow - Green

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Combo

Slide 33

Slide 33 text

GKStateMachine Combo

Slide 34

Slide 34 text

GKStateMachine Combo State 1 State 2 State 3

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Usecase

Slide 41

Slide 41 text

Usecase Init 21/07/2019

Slide 42

Slide 42 text

Usecase Init 21/07/2019

Slide 43

Slide 43 text

Usecase Init 21/07/2019

Slide 44

Slide 44 text

Usecase Init 21/07/2019

Slide 45

Slide 45 text

Init State + Fraud Check System

Slide 46

Slide 46 text

Init Init State + Fraud Check System

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

- Offer Rule System - Check for offer window - Minimum Amount Check - Other set of rules - Order Completion - Book a cab - Order food Other Usecases

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

Thank You Credits
 
 Abhijit K G
 Aditya Rohan