Habito: The Purely Functional Mortgage Broker Will Jones, VP - - PowerPoint PPT Presentation

habito the purely functional mortgage broker
SMART_READER_LITE
LIVE PREVIEW

Habito: The Purely Functional Mortgage Broker Will Jones, VP - - PowerPoint PPT Presentation

Habito: The Purely Functional Mortgage Broker Will Jones, VP Engineering 1 Deck title, edit in View > Master Facts and figures Founded in early 2015, live since early 2016 Completely free service to take the pain out of


slide-1
SLIDE 1

Deck title, edit in View > Master

Habito: The Purely Functional Mortgage Broker

Will Jones, VP Engineering

1

slide-2
SLIDE 2

Habito: The Purely Functional Mortgage Broker 2

  • Founded in early 2015, live since early 2016
  • Completely free service to take the pain out of mortgages
  • Brokered over £1bn in applications to date
  • ~140 people total, ~40 engineers
  • Operate in small, cross-functional teams (~7 at present)

Facts and figures

slide-3
SLIDE 3

Habito: The Purely Functional Mortgage Broker

  • No clear universal language
  • Coupled inheritance hierarchies
  • Complex runtime state
  • Boilerplate

3

Old wounds

slide-4
SLIDE 4

Habito: The Purely Functional Mortgage Broker

  • No clear universal language

Rich, data-driven domain model

  • Coupled inheritance hierarchies

Compose simpler building blocks

  • Complex runtime state

Immutability by default

  • Boilerplate

Code generation from specifications

4

New beginnings

slide-5
SLIDE 5

Habito: The Purely Functional Mortgage Broker 5

Haskell

  • Purely functional programming language
  • Strong static typing
  • Non-strict evaluation model
  • Deploy binaries in Docker containers (for example)
slide-6
SLIDE 6

Habito: The Purely Functional Mortgage Broker

  • “A transaction is either a purchase or a remortgage. A purchase involves a deposit and a property
  • value. A remortgage involves a remaining balance, current monthly repayment and a property value.”

6

Domain modelling

slide-7
SLIDE 7

Habito: The Purely Functional Mortgage Broker

  • “A transaction is either a purchase or a remortgage. A purchase involves a deposit and a property
  • value. A remortgage involves a remaining balance, current monthly repayment and a property value.”

data Txn = Purchase PurchaseTxn | Remo RemoTxn

7

Domain modelling

slide-8
SLIDE 8

Habito: The Purely Functional Mortgage Broker

  • “A transaction is either a purchase or a remortgage. A purchase involves a deposit and a property
  • value. A remortgage involves a remaining balance, current monthly repayment and a property value.”

data Txn = Purchase PurchaseTxn | Remo RemoTxn data PurchaseTxn = PurchaseTxn { deposit :: GBP , propVal :: GBP }

8

Domain modelling

slide-9
SLIDE 9

Habito: The Purely Functional Mortgage Broker

  • “A transaction is either a purchase or a remortgage. A purchase involves a deposit and a property
  • value. A remortgage involves a remaining balance, current monthly repayment and a property value.”

data Txn = Purchase PurchaseTxn | Remo RemoTxn data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP }

9

Domain modelling

slide-10
SLIDE 10

Habito: The Purely Functional Mortgage Broker

  • “A transaction is either a purchase or a remortgage. A purchase involves a deposit and a property
  • value. A remortgage involves a remaining balance, current monthly repayment and a property value.”

data Txn = Purchase PurchaseTxn | Remo RemoTxn txn1 :: Txn data PurchaseTxn txn1 = PurchaseTxn = Purchase (PurchaseTxn { deposit :: GBP { deposit = 30000 , propVal :: GBP , propVal = 100000 } })

10

Domain modelling

slide-11
SLIDE 11

Habito: The Purely Functional Mortgage Broker

  • “Applicant credit policy rule: buy-to-let customers are not eligible for a mortgage if

they are retired or will enter retirement before the end of the mortgage term.”

11

Domain modelling

slide-12
SLIDE 12

Habito: The Purely Functional Mortgage Broker

  • “Applicant credit policy rule: buy-to-let customers are not eligible for a mortgage if

they are retired or will enter retirement before the end of the mortgage term.” rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4"

12

Domain modelling

slide-13
SLIDE 13

Habito: The Purely Functional Mortgage Broker

  • “Applicant credit policy rule: buy-to-let customers are not eligible for a mortgage if

they are retired or will enter retirement before the end of the mortgage term.” rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ rejectIf $

13

Domain modelling

slide-14
SLIDE 14

Habito: The Purely Functional Mortgage Broker

  • “Applicant credit policy rule: buy-to-let customers are not eligible for a mortgage if

they are retired or will enter retirement before the end of the mortgage term.” rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ rejectIf $ empType .== retired .|| derive ageAtEndOfTerm .> retirementAge

14

Domain modelling

slide-15
SLIDE 15

Habito: The Purely Functional Mortgage Broker 15

Simpler building blocks

rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ rejectIf $ empType .== retired .|| derive ageAtEndOfTerm .> retirementAge

  • Domain-specific language
slide-16
SLIDE 16

Habito: The Purely Functional Mortgage Broker 16

Simpler building blocks

rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ rejectIf $ empType .== retired .|| derive ageAtEndOfTerm .> retirementAge

  • Domain-specific language
  • Just a big function composition
slide-17
SLIDE 17

Habito: The Purely Functional Mortgage Broker 17

Simpler building blocks

rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ rejectIf $ empType .== retired .|| derive ageAtEndOfTerm .> retirementAge

  • Domain-specific language
  • Just a big function composition
  • Some power tools: overloading, types
slide-18
SLIDE 18

Habito: The Purely Functional Mortgage Broker 18

Simpler building blocks

rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ rejectIf $ empType .== retired .|| derive ageAtEndOfTerm .> retirementAge

  • Domain-specific language
  • Just a big function composition
  • Some power tools: overloading, types

{ "ruleId": "R-PD-4", "log": [ { "name": "txnScenario", "value": "BuyToLet", "entryType": { "type": "TxnParam" } }, { "name": "Applicants/Primary/DoB", "value": "Just 1945-10-21", "entryType": { "type": "DataKey" } }, ... ], "result": { "type": "Reject" } }

slide-19
SLIDE 19

Habito: The Purely Functional Mortgage Broker 19

Immutability everywhere

  • Verify once, trust elsewhere
  • Useful for parallelism/concurrency
  • In general: easier to reason about
  • Why stop with our language?
slide-20
SLIDE 20

Habito: The Purely Functional Mortgage Broker 20

Data

account_id email password created verified 05d1100a-... will@example <hash> 2018-11-22T.. t dbc85161-... dev@example <hash> 2018-11-21T.. f account profile_id account_id first_name 3df81575-... 05d1100a-... William profile

slide-21
SLIDE 21

Habito: The Purely Functional Mortgage Broker 21

Data

account_id email password created verified 05d1100a-... will@example <hash> 2018-11-22T.. t dbc85161-... dev@example <hash> 2018-11-21T.. t account profile_id account_id first_name 3df81575-... 05d1100a-... William profile

slide-22
SLIDE 22

Habito: The Purely Functional Mortgage Broker 22

Event sourcing

2018-11-21T... 05d1100a-... 1 Account { “type”: “AccountCreated”, “value”: { “email”: “will@example”, “password”: “<hash>” } } 2018-11-21T... 05d1100a-... 2 Account { “type”: “PasswordChanged”, “value”: { “newPassword”: “<hash>” } } 2018-11-22T... 05d1100a-... 3 Account { “type”: “EmailVerified”, “value”: {} }

? Aggregate ID Aggregate version Aggregate type Event data/payload

slide-23
SLIDE 23

Habito: The Purely Functional Mortgage Broker

  • “An account is created. Thereafter the password may be changed, the email may be verified, ...”

data Account = Account { id :: AccId , ... } data AccEvent = Created { id :: AccId } | PassChanged { hashedPass :: HashedPass } | EmailVerified | ...

23

Event sourcing

slide-24
SLIDE 24

Habito: The Purely Functional Mortgage Broker

data Maybe a = Nothing | Just a updateAcc :: Maybe Account -> AccEvent -> Maybe Account updateAcc (Just acc) (PassChanged pwd) = acc { password = pwd } ... buildAcc :: [AccEvent]

  • > Maybe Account

buildAcc = foldl updateAcc Nothing

24

Event sourcing

slide-25
SLIDE 25

Habito: The Purely Functional Mortgage Broker

data Maybe a = Nothing | Just a updateAcc :: Maybe Account -> AccEvent -> Maybe Account updateAcc (Just acc) (PassChanged pwd) = acc { password = pwd } ... buildAcc :: [AccEvent]

  • > Maybe Account

buildAcc = foldl updateAcc Nothing

25

Event sourcing

foldl f z [x1, x2, x3] == f (f (f z x1) x2) x3 foldl updateAcc Nothing [e1, e2, e3] == uA (uA (uA Nothing e1) e2) e3

slide-26
SLIDE 26

Habito: The Purely Functional Mortgage Broker 26

Asking questions

05d1100a-... 1 Account { “type”: “AccountCreated”, “value”: { “email”: “will@example”, “password”: “<hash>” } } 05d1100a-... 2 Account { “type”: “PasswordChanged”, “value”: { “newPassword”: “<hash>” } } 05d1100a-... 3 Account { “type”: “EmailVerified”, “value”: {} } account_id email password created verified 05d1100a-... will@example <hash> 2018-11-22T.. t dbc85161-... dev@example <hash> 2018-11-21T.. f

slide-27
SLIDE 27

Habito: The Purely Functional Mortgage Broker 27

Asking questions

05d1100a-... 1 Account { “type”: “AccountCreated”, “value”: { “email”: “will@example”, “password”: “<hash>” } } 05d1100a-... 2 Account { “type”: “PasswordChanged”, “value”: { “newPassword”: “<hash>” } } 05d1100a-... 3 Account { “type”: “EmailVerified”, “value”: {} }

{ “id”: “05d1100a-...”, “email”: “will@example”, “created”: “2018-11-22T..”, “verified”: true, ... }

slide-28
SLIDE 28

Habito: The Purely Functional Mortgage Broker 28

Asking questions

05d1100a-... 1 Account { “type”: “AccountCreated”, “value”: { “email”: “will@example”, “password”: “<hash>” } } 05d1100a-... 2 Account { “type”: “PasswordChanged”, “value”: { “newPassword”: “<hash>” } } 05d1100a-... 3 Account { “type”: “EmailVerified”, “value”: {} }

{ “id”: “05d1100a-...”, “email”: “will@example”, “created”: “2018-11-22T..”, “verified”: true, ... } runProjection “Accounts” $ allEvents @AccountEvent .| ... .| ... .| ... .| sinkToPostgreSQL “tbl_acc”

slide-29
SLIDE 29

Habito: The Purely Functional Mortgage Broker 29

Asking questions

05d1100a-... 1 Account { “type”: “AccountCreated”, “value”: { “email”: “will@example”, “password”: “<hash>” } } 05d1100a-... 2 Account { “type”: “PasswordChanged”, “value”: { “newPassword”: “<hash>” } } 05d1100a-... 3 Account { “type”: “EmailVerified”, “value”: {} }

{ “id”: “05d1100a-...”, “email”: “will@example”, “created”: “2018-11-22T..”, “verified”: true, ... } runProjection “Accounts” $ allEvents @AccountEvent .| ... .| ... .| ... .| sinkToElastic “idx_acc”

slide-30
SLIDE 30

Habito: The Purely Functional Mortgage Broker 30

Asking questions

05d1100a-... 1 Account { “type”: “AccountCreated”, “value”: { “email”: “will@example”, “password”: “<hash>” } } 05d1100a-... 2 Account { “type”: “PasswordChanged”, “value”: { “newPassword”: “<hash>” } } 05d1100a-... 3 Account { “type”: “EmailVerified”, “value”: {} }

{ “id”: “05d1100a-...”, “email”: “will@example”, “created”: “2018-11-22T..”, “verified”: true, ... } runProjection “Accounts” $ allEvents @AccountEvent .| loggedToGrafana .| concurrently .| batched .| sinkToElastic “idx_acc”

slide-31
SLIDE 31

Habito: The Purely Functional Mortgage Broker

data Txn = Purchase PurchaseTxn | Remo RemoTxn data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP }

31

Boilerplate

slide-32
SLIDE 32

Habito: The Purely Functional Mortgage Broker

data Txn = Purchase PurchaseTxn | Remo RemoTxn deriving (Generic) data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP deriving (Generic) } deriving (Generic)

32

Boilerplate

slide-33
SLIDE 33

Habito: The Purely Functional Mortgage Broker

data Txn = Purchase PurchaseTxn | Remo RemoTxn deriving (Generic) deriving (FromJSON, ToJSON) via (Generically Txn) data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP deriving (Generic) } deriving (Generic)

33

Boilerplate

slide-34
SLIDE 34

Habito: The Purely Functional Mortgage Broker

updateTxnPropVal :: GBP -> Txn -> Txn updateTxnPropVal x txn = data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP deriving (Generic) } deriving (Generic)

34

Boilerplate

slide-35
SLIDE 35

Habito: The Purely Functional Mortgage Broker

updateTxnPropVal :: GBP -> Txn -> Txn updateTxnPropVal x txn = case txn of PurchaseTxn pTxn -> ... RemoTxn rTxn -> ... data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP deriving (Generic) } deriving (Generic)

35

Boilerplate

slide-36
SLIDE 36

Habito: The Purely Functional Mortgage Broker

updateTxnPropVal :: GBP -> Txn -> Txn updateTxnPropVal x txn = set (nestedField @“propVal”) x txn data PurchaseTxn data RemoTxn = PurchaseTxn = RemoTxn { deposit :: GBP { balance :: GBP , propVal :: GBP , currMonthly :: GBP } , propVal :: GBP deriving (Generic) } deriving (Generic)

36

Boilerplate

slide-37
SLIDE 37

Habito: The Purely Functional Mortgage Broker

Compiling domains

37

rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ ...

{ "ruleId": "R-PD-4", "log": [ { "name": "txnScenario", "value": "BuyToLet", "entryType": { "type": "TxnParam" } }, ... ], "result": { "type": "Reject" } }

slide-38
SLIDE 38

Habito: The Purely Functional Mortgage Broker

Compiling domains

38

rPD4 :: (HasToday, HasApplicant) => RuleBuilder "R-PD-4" rPD4 _ = given (txnParam @"txnScenario" .== buyToLet) $ ...

{ "ruleId": "R-PD-4", "log": [ { "name": "txnScenario", "value": "BuyToLet", "entryType": { "type": "TxnParam" } }, ... ], "result": { "type": "Reject" } }

slide-39
SLIDE 39

Habito: The Purely Functional Mortgage Broker 39

It can’t all be good news

  • Type-checking and compilation times
  • Laziness and reasoning about performance
  • Language ecosystem -- tooling and libraries
  • Recruitment and hiring
  • Event sourcing has its own challenges -- reprojection
slide-40
SLIDE 40

Habito: The Purely Functional Mortgage Broker

  • No clear universal language

Rich, data-driven domain model

  • Coupled inheritance hierarchies

Compose simpler building blocks

  • Complex runtime state

Immutability by default

  • Boilerplate

Code generation from specifications

40

Useful underpinnings

slide-41
SLIDE 41

Deck title, edit in View > Master

Thank you

41