Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Habito: The Purely Functional Mortgage Broker

86 views

Published on

Video and slides synchronized, mp3 and slide download available at URL http://bit.ly/2YXwv4Z.

Will Jones talks about how Habito, the leading digital mortgage broker, benefited from using Haskell, some of the wins and trade-offs that have brought it to where it is today and where it's going next. He also talks about why functional programming is beneficial for large projects, and how it helps especially with migrating the data store. Filmed at qconlondon.com.

Will Jones is a polyglot software engineer and passionate teacher with over eight years' experience building applications, creating products and educating other developers and computer scientists. His passions are deeply embedded in the technologies he is using in his current role as VP Engineering at Habito, such as Haskell, PureScript and event sourcing/CQRS.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Habito: The Purely Functional Mortgage Broker

  1. 1. Deck title, edit in View > Master Habito: The Purely Functional Mortgage Broker Will Jones, VP Engineering 1
  2. 2. InfoQ.com: News & Community Site Watch the video with slide synchronization on InfoQ.com! https://www.infoq.com/presentations/ habito-mortgage-broker/ • Over 1,000,000 software developers, architects and CTOs read the site world- wide every month • 250,000 senior developers subscribe to our weekly newsletter • Published in 4 languages (English, Chinese, Japanese and Brazilian Portuguese) • Post content from our QCon conferences • 2 dedicated podcast channels: The InfoQ Podcast, with a focus on Architecture and The Engineering Culture Podcast, with a focus on building • 96 deep dives on innovative topics packed as downloadable emags and minibooks • Over 40 new content items per week
  3. 3. Purpose of QCon - to empower software development by facilitating the spread of knowledge and innovation Strategy - practitioner-driven conference designed for YOU: influencers of change and innovation in your teams - speakers and topics driving the evolution and innovation - connecting and catalyzing the influencers and innovators Highlights - attended by more than 12,000 delegates since 2007 - held in 9 cities worldwide Presented at QCon London www.qconlondon.com
  4. 4. 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
  5. 5. Habito: The Purely Functional Mortgage Broker ● No clear universal language ● Coupled inheritance hierarchies ● Complex runtime state ● Boilerplate 3 Old wounds
  6. 6. 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
  7. 7. 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)
  8. 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.” 6 Domain modelling
  9. 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 7 Domain modelling
  10. 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 data PurchaseTxn = PurchaseTxn { deposit :: GBP , propVal :: GBP } 8 Domain modelling
  11. 11. 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
  12. 12. 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
  13. 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.” 11 Domain modelling
  14. 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" 12 Domain modelling
  15. 15. 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
  16. 16. 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
  17. 17. 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
  18. 18. 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
  19. 19. 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
  20. 20. 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" } }
  21. 21. 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?
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. 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
  29. 29. 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, ... }
  30. 30. 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”
  31. 31. 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”
  32. 32. 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”
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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" } }
  40. 40. 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" } }
  41. 41. 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
  42. 42. 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
  43. 43. Deck title, edit in View > Master Thank you 41
  44. 44. Watch the video with slide synchronization on InfoQ.com! https://www.infoq.com/presentations/ habito-mortgage-broker/

×