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

ML 21: Experience Report: Domain Modeling with F#

ML 21: Experience Report: Domain Modeling with F#

I describe my experience using algebraic data types in F# for modeling and communication of domain concepts between a development team, domain experts, and other stakeholders. This experience was very successful, and I describe the reasons why F# is well suited for this.

Scott Wlaschin

August 25, 2021
Tweet

More Decks by Scott Wlaschin

Other Decks in Technology

Transcript

  1. Business-focused development • Understanding the domain is critical – “Correctness”

    is a lesser goal • Responding to feedback – Need to adjust domain model quickly and safely
  2. F# for business-focused development • Readable by non-developers – And

    easy to collaborate with them • Composable types for domain modeling – And type checking makes it safe to refactor • Eliminating errors through design – “Make illegal states unrepresentable”
  3. Shared language What DDD source code should look like module

    CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King type Card = Suit * Rank type Hand = Card list type Deck = Card list type Player = { Name:string; Hand:Hand } type Game = { Deck:Deck; Players: Player list } type Deal = Deck –› (Deck * Card) type PickupCard = (Hand * Card) –› Hand
  4. module CardGame = type Suit = Club | Diamond |

    Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King type Card = Suit * Rank type Hand = Card list type Deck = Card list type Player = { Name:string; Hand:Hand } type Game = { Deck:Deck; Players: Player list } type Deal = Deck –› (Deck * Card) type PickupCard = (Hand * Card) –› Hand
  5. module CardGame = type Suit = Club | Diamond |

    Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King type Card = Suit * Rank type Hand = Card list type Deck = Card list type Player = { Name:string; Hand:Hand } type Game = { Deck:Deck; Players: Player list } type Deal = Deck –› (Deck * Card) type PickupCard = (Hand * Card) –› Hand Non-programmers can provide useful feedback
  6. Creating the domain model is an interactive process Not just

    for developers. Everyone participates
  7. ... type Deck = Card list type Deal = ShuffledDeck

    –› (ShuffledDeck * Card) type ShuffledDeck = Card list
  8. ... type Deck = Card list type Deal = ShuffledDeck

    –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck
  9. ... type Deck = Card list type Deal = ShuffledDeck

    –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck
  10. I explain that new types are built from smaller types

    by: • Composing with AND • Composing with OR (“choices”)
  11. Example of requirements: We accept three forms of payment: Cash,

    PayPal, or Card. For Cash we don't need any extra information For PayPal we need a email address For Cards we need a card type and card number
  12. type EmailAddress = ... type CardNumber = … type CardType

    = Visa | Mastercard type CreditCardInfo = { CardType : CardType CardNumber : CardNumber }
  13. type EmailAddress = ... type CardNumber = ... type CardType

    = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo
  14. type EmailAddress = ... type CardNumber = ... type CardType

    = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo
  15. type EmailAddress = ... type CardNumber = ... type CardType

    = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD
  16. type EmailAddress = ... type CardNumber = ... type CardType

    = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD type Payment = { Amount : PaymentAmount Currency : Currency Method : PaymentMethod }
  17. type EmailAddress = ... type CardNumber = ... type CardType

    = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD type Payment = { Amount : PaymentAmount Currency : Currency Method : PaymentMethod }
  18. Business Rules: • Rule 1: If the email is changed,

    the verified flag must be reset to false. • Rule 2: The verified flag can only be set by a special verification service type EmailContactInfo = { EmailAddress : string IsEmailVerified : bool }
  19. type EmailAddress = EmailAddress of string // with associated smart

    constructor type VerifiedEmail = VerifiedEmail of EmailAddress type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail Here’s the refactored model
  20. type EmailAddress = EmailAddress of string // with associated smart

    constructor type VerifiedEmail = VerifiedEmail of EmailAddress type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  21. type EmailAddress = EmailAddress of string // with associated smart

    constructor type VerifiedEmail = VerifiedEmail of EmailAddress type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  22. type EmailAddress = EmailAddress of string // with associated smart

    constructor type VerifiedEmail = VerifiedEmail of EmailAddress type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail Those business rules are automatically enforced by the design!
  23. Nice-to-have language improvements • Zero-cost wrappers (e.g. newtype) • An

    easier way to do “smart constructors” that perform validation • An easier way to enforce constraints (refinement types?) • A more fine-grained way of controlling access to constructors (e.g. the VerifiedEmail constructor)
  24. Summary • Algebraic types are excellent for business-focused domain modeling

    • Non-developers can read and contribute • Lots of potential for ML languages here! Thanks!