O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Domain Modeling with FP (DDD Europe 2020)

1.484 visualizações

Publicada em

Functional programming can be an excellent approach to designing decoupled, reusable systems with a rich domain model. In fact, the lessons from applying DDD in a functional language translate well to object-oriented programming.

Publicada em: Software

Domain Modeling with FP (DDD Europe 2020)

  1. 1. Domain Modeling With Functional Programming (DDD Europe 2020) @ScottWlaschin fsharpforfunandprofit.com
  2. 2. Functional Programming Domain Driven Design
  3. 3. FP is good for: • Interactive & collaborative domain modeling • Representing a domain model accurately
  4. 4. Part I The importance of design
  5. 5. Input Output Process The software development process
  6. 6. Input Output Process The software development process
  7. 7. Input Output Process Garbage in Garbage out The software development process
  8. 8. Input Output Process (reduce) Garbage in (reduced) Garbage out The software development process
  9. 9. • Agile contribution: – Rapid feedback during design • DDD contribution: – Stakeholders have a shared mental model – …which is also represented in the code How can we do design right?
  10. 10. Can you really make code represent the domain?
  11. 11. What non-developers think source code looks like
  12. 12. 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 Sharedlanguage What DDD source code should look like
  13. 13. 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 * means a pair. Choose one from each type list type is built in
  14. 14. Deal (original) Deck (remaining) Deck (on table) Card Modeling an action with a function type Deal = Deck -> (Deck * Card) Input Output
  15. 15. Pickup Card (updated) Hand (original) Hand (on table) Card Modeling an action with a function type PickupCard = (Hand * Card) –> Hand Input Output
  16. 16. 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
  17. 17. 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
  18. 18. 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 Can non-programmers provide useful feedback?
  19. 19. Rapid feedback during the design stage
  20. 20. Building a shared mental model is an interactive process
  21. 21. ... type Deck = Card list type Deal = Deck –› (Deck * Card)
  22. 22. ... type Deck = Card list type Deal = ShuffledDeck –› (ShuffledDeck * Card)
  23. 23. ... type Deck = Card list type Deal = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list
  24. 24. ... type Deck = Card list type Deal = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck
  25. 25. ... type Deck = Card list type Deal = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck
  26. 26. Final version of the domain
  27. 27. module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two |Three | Four | Five | Six | Seven | Eight | ... 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 = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck type PickupCard = (Hand * Card) –› Hand
  28. 28. module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two |Three | Four | Five | Six | Seven | Eight | ... 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 = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck type PickupCard = (Hand * Card) –› Hand
  29. 29. In the real world Suit Rank Card Hand Deck Player Deal In the code Suit Rank Card Hand Deck Player Deal
  30. 30. In the real world Suit Rank Card Hand Deck Player Deal In the code Suit Rank Card Hand Deck Player Deal ShuffledDeck Shuffle ShuffledDeck Shuffle 
  31. 31. In the real world Suit Rank Card Hand Deck Player Deal In the code Suit Rank Card Hand Deck Player Deal PlayerController DeckBase AbstractCardProxyFactoryBean 
  32. 32. module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two |Three | Four | Five | Six | Seven | Eight | ... 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 = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck type PickupCard = (Hand * Card) –› Hand
  33. 33. module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two |Three | Four | Five | Six | Seven | Eight | ... 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 = ShuffledDeck –› (ShuffledDeck * Card) type ShuffledDeck = Card list type Shuffle = Deck –› ShuffledDeck type PickupCard = (Hand * Card) –› Hand
  34. 34. The process of building the shared mental model is critical! Collaboration!
  35. 35. Key DDD principle: Communicate the design in the code
  36. 36. A domain modeling challenge!
  37. 37. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } // true if ownership of // email address is confirmed
  38. 38. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: which values are optional?
  39. 39. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: what are the constraints?
  40. 40. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: domain logic?
  41. 41. Prologue: F# can help type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }
  42. 42. Prologue: F# can help type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }
  43. 43. Part II Understanding FP type systems
  44. 44. FP principle: Composition everywhere
  45. 45. Composition = Make big things from small things
  46. 46. Algebraic type system
  47. 47. New types are built from smaller types by: Composing with “AND” Composing with “OR”
  48. 48. FruitSalad = One each of and and Compose with “AND” type FruitSalad = { Apple: AppleInfo Banana: BananaInfo Cherry: CherryInfo }
  49. 49. Snack = or or Compose with “OR” type Snack = | Apple of AppleInfo | Banana of BananaInfo | Cherry of CherryInfo
  50. 50. Part III Domain modeling with composable types
  51. 51. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } // true if ownership of // email address is confirmed This looks suspiciously like database-driven design...
  52. 52. Conversation-driven design
  53. 53. Conversation-driven design
  54. 54. type Contact = { Name: PersonalName Email: EmailContactInfo } "A contact has a name AND email address" "Like this?..."
  55. 55. type Contact = { Name: PersonalName Email: EmailContactInfo } "A contact has a name AND email address" We have two new concepts already!
  56. 56. type PersonalName = { FirstName: string MiddleInitial: string LastName: string } "What's a personal name?"
  57. 57. type PersonalName = { FirstName: string MiddleInitial: string LastName: string } required required optional "What's required or optional?"
  58. 58. Modeling optional values
  59. 59. type Option<'T> = | Some of 'T | None
  60. 60. type PersonalName = { FirstName: string MiddleInitial: Option<string> LastName: string }
  61. 61. type PersonalName = { FirstName: string MiddleInitial: string option LastName: string }
  62. 62. Modeling simple values and constrained values
  63. 63. Modeling simple values • Avoid "Primitive Obsession" • Simple values should not be modelled with primitive types like "int" or "string" or "float" "Does 'float' have something to do with water?"
  64. 64. Modeling constrained values • It's rare to have an unconstrained int or string: – An EmailAddress must not be empty, it must match a pattern – A PhoneNumber must not be empty, it must match a pattern – A CustomerId must be a positive integer
  65. 65. Is an EmailAddress just a string? No! Is a CustomerId just a int? No!
  66. 66. type EmailAddress = EmailAddress of string Use wrapper types to keep domain concepts distinct from their representation type CustomerId = CustomerId of int type String50 = String50 of string
  67. 67. type EmailAddress = EmailAddress of string type PhoneNumber = PhoneNumber of string type CustomerId = CustomerId of int type OrderId = OrderId of int Two benefits: - Clearer domain modelling - Can't mix them up accidentally
  68. 68. The "Contact" challenge, after first refactor
  69. 69. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }
  70. 70. type Contact = { FirstName: string MiddleInitial: string option LastName: string EmailAddress: string IsEmailVerified: bool } Use option type for potentially missing values
  71. 71. type Contact = { FirstName: String50 MiddleInitial: String1 option LastName: String50 EmailAddress: EmailAddress IsEmailVerified: bool } Use wrapper types instead of primitives
  72. 72. type PersonalName = { FirstName : String50 MiddleInitial : String1 option LastName : String50 } type EmailContactInfo = { EmailAddress : EmailAddress IsEmailVerified : bool } Aggregates a.k.a. "consistency boundaries" 2 different domain concepts
  73. 73. Replacing flags with choices
  74. 74. What about this? type EmailContactInfo = { EmailAddress : EmailAddress IsEmailVerified : bool }
  75. 75. • 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 : EmailAddress IsEmailVerified : bool }
  76. 76. "Email contact info is either Verified OR Unverified" Listen closely to what the domain expert says... type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  77. 77. "Email contact info is either Verified OR Unverified" type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  78. 78. "Email contact info is either Verified OR Unverified" type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  79. 79. Q: Is a Verified email different? Are there different business rules? A: Yes, it must not be mixed up with unverified. type VerifiedEmail = VerifiedEmail of EmailAddress "there is no problem that can’t be solved by wrapping it in another type"
  80. 80. type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option Q: Where do we get Verified emails from? A: A special verification process
  81. 81. type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option Q: Are the business rules clear now?
  82. 82. type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option Those business rules are automatically enforced by the design!
  83. 83. The "Contact" challenge, completed
  84. 84. type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo }
  85. 85. type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo }
  86. 86. type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo } type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  87. 87. type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo } type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  88. 88. type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo } type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  89. 89. type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo } type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  90. 90. type PersonalName = { FirstName: String50 MiddleInitial: String1 opt LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo } type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail
  91. 91. Summary • Represent the shared mental model in code – The developers should become domain experts too – Write code collaboratively to build the shared mental model • Use the power of a composable type system – Options instead of null – Wrappers for constrained types – Choice types rather than inheritance or flags – Static types give you confidence when refactoring
  92. 92. More "Domain Modeling Made Functional" at – fsharpforfunandprofit.com/ddd Thanks! Twitter: @ScottWlaschin

×