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.
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool
} // ...
Domain Modeling
Made Functional
(KanDDDinsky 2019)
@ScottWlaschin
fsharpforfunandprofit.com
Functional
Programming
Domain
Driven
Design
Functional programming is good for...
Boring
Line Of Business
Applications
(BLOBAs)
Part I
The importance of design
Input Output
Process
The software development process
Input Output
Process
The software development process
Input Output
Process
Garbage in Garbage out
The software development process
Input Output
Process
(reduce) Garbage in (reduced) Garbage out
The software development process
• Agile contribution:
– Rapid feedback during design
• DDD contribution:
– Stakeholders have a shared mental model
– …whic...
Can you really make code
represent the domain?
What non-developers think source code looks like
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| ...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| ...
Deal
(original)
Deck
(remaining)
Deck
(on table)
Card
Modeling an action with a function
type Deal = Deck -> (Deck * Card)...
Pickup Card
(updated)
Hand
(original)
Hand
(on table)
Card
Modeling an action with a function
type PickupCard = (Hand * Ca...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| ...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| ...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| ...
Rapid feedback during
the design stage
...
type Deck = Card list
type Deal = Deck –› (Deck * Card)
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
type ShuffledDeck = Card list
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
type ShuffledDeck = Card list
type Shuffle = D...
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
type ShuffledDeck = Card list
type Shuffle = D...
Final version of the domain
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | ...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | ...
In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
ShuffledDeck
Shuff...
In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
PlayerManager
Deck...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | ...
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | ...
Key DDD principle:
Communicate the design
in the code
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool
}
Pro...
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool
}
Pro...
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool
}
Pro...
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool
}
Pro...
Prologue: F# can help
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEma...
Prologue: F# can help
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEma...
Part II
Understanding FP type systems
An introduction to “algebraic” typesAn introduction to “algebraic” types
FP principle:
Types are not classes
The Tunnel of
Transformation
Function
apple -> banana
A function is a thing which
transforms inputs to outputs
So, what is a type then?
A type is a just a name
for a set of things
Set of
valid inputs
Set of
valid outputs
Function
Set of
valid inputs
Set of
valid outputs
Function
1
2
3
4
5
6
This is type
"integer"
A type is a just a name
for a set of ...
Set of
valid inputs
Set of
valid outputs
Function
This is type
"string"
"abc"
"but"
"cobol"
"double"
"end"
"float"
A type ...
Set of
valid inputs
Set of
valid outputs
Function
This is type
"Person"
Donna Roy
Javier Mendoza
Nathan Logan
Shawna Ingra...
Set of
valid inputs
Set of
valid outputs
Function
This is type
"Fruit"
A type is a just a name
for a set of things
Set of
valid inputs
Set of
valid outputs
Function
This is a type of
Fruit->Fruit functions
A type is a just a name
for a s...
FP principle:
Composition everywhere
All pieces are designed to be connected
Algebraic type system
New types are built from smaller types by:
Composing with “AND”
Composing with “OR”
Example: pairs, tuples, records
FruitSalad = One each of and and
Compose with “AND”
type FruitSalad = {
Apple: AppleVariet...
Snack = or or
Compose with “OR”
type Snack =
| Apple of AppleVariety
| Banana of BananaVariety
| Cherry of CherryVariety
A real world example
of composing types
Some requirements:
We accept three forms of payment:
Cash, Check, or Card.
For Cash we don't need any extra information
Fo...
interface IPaymentMethod
{..}
class Cash() : IPaymentMethod
{..}
class Check(int checkNo): IPaymentMethod
{..}
class Card(...
type CheckNumber = int
type CardNumber = string
In FP you would probably implement by composing
types, like this:
type CheckNumber = ...
type CardNumber = …
type CardType = Visa | Mastercard
type CreditCardInfo = {
CardType : CardType
C...
type CheckNumber = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| C...
type CheckNumber = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| C...
type CheckNumber = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| C...
type CheckNumber = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| C...
What are types for?
An annotation to a value for type checking
type AddOne: int –› int
Domain modelling tool
type Deal = D...
STATICALLY TYPE ALL
THE THINGS
Part III
Domain modeling
with composable types
What can we do with this type system?
Modeling
optional values
type PersonalName = {
FirstName: string
MiddleInitial: string
LastName: string
}
required
required
optional
Length
string –› int
“a”
“b”
“c”
1
2
3
“a”
“b”
“c”
null
Length
string –› int
“a”
“b”
“c”
null
1
2
3
Length
string –› int
“a”
“b”
“c”
null
1
2
3
X
+=
“a”
“b”
“c”
“a”
“b”
“c”
missing
or
Tag with “Nothing”
type OptionalString =
| SomeString of string
| Nothing
type OptionalInt =
| SomeInt of int
| Nothing
type OptionalString =
| SomeString of string
| Nothing
type OptionalBool =
|...
type PersonalName = {
FirstName: string
MiddleInitial: string
LastName: string
}
type Option<'T> =
| Some of 'T
| None
type PersonalName = {
FirstName: string
MiddleInitial: Option<string>
LastName: string
}
type Option<'T> =
| Some of 'T
| ...
type PersonalName = {
FirstName: string
MiddleInitial: string option
LastName: string
}
type Option<'T> =
| Some of 'T
| N...
Modeling simple values and
constrained values
Modeling simple values
• Avoid "Primitive Obsession"
– Simple values should not be modelled with
primitive types
– "Does '...
Modeling constrained values
• Rare to have an unbounded integer or string!
Generally constrained in some way:
– Emails mus...
type Email = Email of string
Is an EmailAddress just a string? No!
Is a CustomerId just a int? No!
Use wrapper types to ke...
type EmailAddress = EmailAddress of string
type PhoneNumber = PhoneNumber of string
type CustomerId = CustomerId of int
ty...
let createEmailAddress (s:string) =
if s.Contains("@")
then (EmailAddress s)
else ?
createEmailAddress:
string –› EmailAdd...
let createEmailAddress (s:string) =
if s.Contains("@")
then Some (EmailAddress s)
else None
createEmailAddress:
string –› ...
type String50 = String50 of string
let createString50 (s:string) =
if s.Length <= 50
then Some (String50 s)
else None
crea...
+– 999999Qty: Add To Cart
type OrderLineQty = OrderLineQty of int
let createOrderLineQty qty =
if qty > 0 && qty <= 99
then Some (OrderLineQty qty)
...
The "Contact" challenge,
revisited
type Contact = {
FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool
}
type Contact = {
FirstName: string
MiddleInitial: string option
LastName: string
EmailAddress: string
IsEmailVerified: boo...
type Contact = {
FirstName: String50
MiddleInitial: String1 option
LastName: String50
EmailAddress: EmailAddress
IsEmailVe...
type Contact = {
CustomerId : CustomerId
FirstName: String50
MiddleInitial: String1 option
LastName: String50
EmailAddress...
type PersonalName = {
CustomerId : CustomerId
FirstName : String50
MiddleInitial : String1 option
LastName : String50 }
ty...
What about this?
Aggregates a.k.a. "consistency boundaries"
type PersonalName = {
CustomerId : CustomerId
FirstName : Stri...
Replacing flags with choices
• Rule 1: If the email is changed, the verified flag
must be reset to false.
• Rule 2: The verified flag can only be set b...
type VerifiedEmail =
VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) –› Verifie...
type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of Veri...
type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of Veri...
type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of Veri...
The "Contact" challenge,
completed
type EmailAddress = ...
type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAd...
type EmailAddress = ...
type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAd...
type PersonalName = {
FirstName: String50
MiddleInitial: String1 opt
LastName: String50 }
type Contact = {
Name: PersonalN...
type PersonalName = {
FirstName: String50
MiddleInitial: String1 opt
LastName: String50 }
type Contact = {
Name: PersonalN...
type PersonalName = {
FirstName: String50
MiddleInitial: String1 opt
LastName: String50 }
type Contact = {
Name: PersonalN...
type PersonalName = {
FirstName: String50
MiddleInitial: String1 opt
LastName: String50 }
type Contact = {
Name: PersonalN...
type PersonalName = {
FirstName: String50
MiddleInitial: String1 opt
LastName: String50 }
type Contact = {
Name: PersonalN...
type VerifiedEmail = ...
type SendPasswordReset =
VerifiedEmail -> ...
Business rule: Only send password resets to verifie...
Part IV
Encoding business rules with types
type Contact = {
Name: Name
Email: EmailContactInfo
Address: PostalContactInfo
}
type Contact = {
Name: Name
Email: EmailContactInfo
Address: PostalContactInfo
}
New rule:
“A contact must have an email o...
type Contact = {
Name: Name
Email: EmailContactInfo option
Address: PostalContactInfo option
}
New rule:
“A contact must h...
"Make illegal states unrepresentable!"
–Yaron Minsky
“A contact must have an email or a postal address”
implies:
• email address only, or
• postal address only, or
• both emai...
type ContactInfo =
| EmailOnly of EmailContactInfo
| AddrOnly of PostalContactInfo
| EmailAndAddr of EmailContactInfo * Po...
Composable types are almost as awesome as this
Communication is two-way.
It's OK to push back
“A contact must have an email or a postal address”
“A contact must have at least one way of being contacted”
“A contact must have at least one way of being contacted”
type Contact = {
Name: Name
PrimaryContactInfo: ContactInfo
Seco...
type ContactInfo =
| Email of EmailContactInfo
| Addr of PostalContactInfo
| Facebook of FacebookInfo
| SMS of PhoneNumber
Summary
• Use code to represent the shared mental model
and ubiquitous language
• Designs will evolve. Embrace change.
– R...
If you liked this talk:
• "Domain Modeling Made Functional"
– fsharpforfunandprofit.com/ddd
• More videos
– fsharpforfunan...
Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)
Domain Modeling Made Functional (KanDDDinsky 2019)
Upcoming SlideShare
Loading in …5
×
Upcoming SlideShare
What to Upload to SlideShare
Next
Download to read offline and view in fullscreen.

8

Share

Download to read offline

Domain Modeling Made Functional (KanDDDinsky 2019)

Download to read offline

(video at https://fsharpforfunandprofit.com/ddd/)

Statically typed functional programming languages encourage a very different way of thinking about types. The type system is your friend, not an annoyance, and can be used in many ways that might not be familiar to OO programmers. Types can be used to represent the domain in a fine-grained, self documenting way. And in many cases, types can even be used to encode business rules so that you literally cannot create incorrect code. You can then use the static type checking almost as an instant unit test — making sure that your code is correct at compile time. In this talk, we'll look at some of the ways you can use types as part of a domain driven design process, with some simple real world examples in F#. No jargon, no maths, and no prior F# experience necessary.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Domain Modeling Made Functional (KanDDDinsky 2019)

  1. 1. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } // true if ownership of // email address is confirmed Domain Modeling Made Functional
  2. 2. Domain Modeling Made Functional (KanDDDinsky 2019) @ScottWlaschin fsharpforfunandprofit.com
  3. 3. Functional Programming Domain Driven Design
  4. 4. Functional programming is good for... Boring Line Of Business Applications (BLOBAs)
  5. 5. Part I The importance of design
  6. 6. Input Output Process The software development process
  7. 7. Input Output Process The software development process
  8. 8. Input Output Process Garbage in Garbage out The software development process
  9. 9. Input Output Process (reduce) Garbage in (reduced) Garbage out The software development process
  10. 10. • 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?
  11. 11. Can you really make code represent the domain?
  12. 12. What non-developers think source code looks 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 Sharedlanguage What DDD source code should look like
  14. 14. 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
  15. 15. Deal (original) Deck (remaining) Deck (on table) Card Modeling an action with a function type Deal = Deck -> (Deck * Card) Input Output
  16. 16. Pickup Card (updated) Hand (original) Hand (on table) Card Modeling an action with a function type PickupCard = (Hand * Card) –> Hand Input Output
  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
  19. 19. 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?
  20. 20. Rapid feedback during the design stage
  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 PlayerManager 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. Key DDD principle: Communicate the design in the code
  35. 35. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: which values are optional?
  36. 36. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: what are the constraints?
  37. 37. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: what groups are atomic?
  38. 38. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: domain logic?
  39. 39. Prologue: F# can help type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }
  40. 40. Prologue: F# can help type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }
  41. 41. Part II Understanding FP type systems An introduction to “algebraic” typesAn introduction to “algebraic” types
  42. 42. FP principle: Types are not classes
  43. 43. The Tunnel of Transformation Function apple -> banana A function is a thing which transforms inputs to outputs
  44. 44. So, what is a type then? A type is a just a name for a set of things Set of valid inputs Set of valid outputs Function
  45. 45. Set of valid inputs Set of valid outputs Function 1 2 3 4 5 6 This is type "integer" A type is a just a name for a set of things
  46. 46. Set of valid inputs Set of valid outputs Function This is type "string" "abc" "but" "cobol" "double" "end" "float" A type is a just a name for a set of things
  47. 47. Set of valid inputs Set of valid outputs Function This is type "Person" Donna Roy Javier Mendoza Nathan Logan Shawna Ingram Abel Ortiz Lena Robbins GordonWood A type is a just a name for a set of things
  48. 48. Set of valid inputs Set of valid outputs Function This is type "Fruit" A type is a just a name for a set of things
  49. 49. Set of valid inputs Set of valid outputs Function This is a type of Fruit->Fruit functions A type is a just a name for a set of things
  50. 50. FP principle: Composition everywhere
  51. 51. All pieces are designed to be connected
  52. 52. Algebraic type system
  53. 53. New types are built from smaller types by: Composing with “AND” Composing with “OR”
  54. 54. Example: pairs, tuples, records FruitSalad = One each of and and Compose with “AND” type FruitSalad = { Apple: AppleVariety Banana: BananaVariety Cherry: CherryVariety }
  55. 55. Snack = or or Compose with “OR” type Snack = | Apple of AppleVariety | Banana of BananaVariety | Cherry of CherryVariety
  56. 56. A real world example of composing types
  57. 57. Some requirements: We accept three forms of payment: Cash, Check, or Card. For Cash we don't need any extra information For Checks we need a check number For Cards we need a card type and card number
  58. 58. interface IPaymentMethod {..} class Cash() : IPaymentMethod {..} class Check(int checkNo): IPaymentMethod {..} class Card(string cardType, string cardNo) : IPaymentMethod {..} In OO design you would probably implement it as an interface and a set of subclasses, like this:
  59. 59. type CheckNumber = int type CardNumber = string In FP you would probably implement by composing types, like this:
  60. 60. type CheckNumber = ... type CardNumber = … type CardType = Visa | Mastercard type CreditCardInfo = { CardType : CardType CardNumber : CardNumber }
  61. 61. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo
  62. 62. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD
  63. 63. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD type Payment = { Amount : PaymentAmount Currency : Currency Method : PaymentMethod }
  64. 64. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD type Payment = { Amount : PaymentAmount Currency : Currency Method : PaymentMethod }
  65. 65. What are types for? An annotation to a value for type checking type AddOne: int –› int Domain modelling tool type Deal = Deck –› (Deck * Card)
  66. 66. STATICALLY TYPE ALL THE THINGS
  67. 67. Part III Domain modeling with composable types What can we do with this type system?
  68. 68. Modeling optional values
  69. 69. type PersonalName = { FirstName: string MiddleInitial: string LastName: string } required required optional
  70. 70. Length string –› int “a” “b” “c” 1 2 3 “a” “b” “c” null
  71. 71. Length string –› int “a” “b” “c” null 1 2 3
  72. 72. Length string –› int “a” “b” “c” null 1 2 3 X
  73. 73. += “a” “b” “c” “a” “b” “c” missing or Tag with “Nothing” type OptionalString = | SomeString of string | Nothing
  74. 74. type OptionalInt = | SomeInt of int | Nothing type OptionalString = | SomeString of string | Nothing type OptionalBool = | SomeBool of bool | Nothing
  75. 75. type PersonalName = { FirstName: string MiddleInitial: string LastName: string } type Option<'T> = | Some of 'T | None
  76. 76. type PersonalName = { FirstName: string MiddleInitial: Option<string> LastName: string } type Option<'T> = | Some of 'T | None
  77. 77. type PersonalName = { FirstName: string MiddleInitial: string option LastName: string } type Option<'T> = | Some of 'T | None
  78. 78. Modeling simple values and constrained values
  79. 79. Modeling simple values • Avoid "Primitive Obsession" – Simple values should not be modelled with primitive types – "Does 'float' have something to do with water?"
  80. 80. Modeling constrained values • Rare to have an unbounded integer or string! Generally constrained in some way: – Emails must not be empty, must match a pattern – CustomerIds must be positive
  81. 81. type Email = Email of string Is an EmailAddress just a string? No! Is a CustomerId just a int? No! Use wrapper types to keep them distinct type CustomerId = CustomerId of int
  82. 82. type EmailAddress = EmailAddress of string type PhoneNumber = PhoneNumber of string type CustomerId = CustomerId of int type OrderId = OrderId of int
  83. 83. let createEmailAddress (s:string) = if s.Contains("@") then (EmailAddress s) else ? createEmailAddress: string –› EmailAddress
  84. 84. let createEmailAddress (s:string) = if s.Contains("@") then Some (EmailAddress s) else None createEmailAddress: string –› EmailAddress option
  85. 85. type String50 = String50 of string let createString50 (s:string) = if s.Length <= 50 then Some (String50 s) else None createString50 : string –› String50 option
  86. 86. +– 999999Qty: Add To Cart
  87. 87. type OrderLineQty = OrderLineQty of int let createOrderLineQty qty = if qty > 0 && qty <= 99 then Some (OrderLineQty qty) else None createOrderLineQty: int –› OrderLineQty option
  88. 88. The "Contact" challenge, revisited
  89. 89. type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }
  90. 90. type Contact = { FirstName: string MiddleInitial: string option LastName: string EmailAddress: string IsEmailVerified: bool }
  91. 91. type Contact = { FirstName: String50 MiddleInitial: String1 option LastName: String50 EmailAddress: EmailAddress IsEmailVerified: bool }
  92. 92. type Contact = { CustomerId : CustomerId FirstName: String50 MiddleInitial: String1 option LastName: String50 EmailAddress: EmailAddress IsEmailVerified: bool } Add an id here 2 different aggregates Aggregates a.k.a. "consistency boundaries"
  93. 93. type PersonalName = { CustomerId : CustomerId FirstName : String50 MiddleInitial : String1 option LastName : String50 } type EmailContactInfo = { CustomerId : CustomerId EmailAddress : EmailAddress IsEmailVerified : bool } Aggregates a.k.a. "consistency boundaries" 2 different aggregates
  94. 94. What about this? Aggregates a.k.a. "consistency boundaries" type PersonalName = { CustomerId : CustomerId FirstName : String50 MiddleInitial : String1 option LastName : String50 } type EmailContactInfo = { CustomerId : CustomerId EmailAddress : EmailAddress IsEmailVerified : bool }
  95. 95. Replacing flags with choices
  96. 96. • 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 }
  97. 97. type VerifiedEmail = VerifiedEmail of EmailAddress type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option
  98. 98. type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option
  99. 99. type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option
  100. 100. 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!
  101. 101. The "Contact" challenge, completed
  102. 102. 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 }
  103. 103. 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 }
  104. 104. 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
  105. 105. 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
  106. 106. 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
  107. 107. 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
  108. 108. 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
  109. 109. type VerifiedEmail = ... type SendPasswordReset = VerifiedEmail -> ... Business rule: Only send password resets to verified emails
  110. 110. Part IV Encoding business rules with types
  111. 111. type Contact = { Name: Name Email: EmailContactInfo Address: PostalContactInfo }
  112. 112. type Contact = { Name: Name Email: EmailContactInfo Address: PostalContactInfo } New rule: “A contact must have an email or a postal address”
  113. 113. type Contact = { Name: Name Email: EmailContactInfo option Address: PostalContactInfo option } New rule: “A contact must have an email or a postal address”
  114. 114. "Make illegal states unrepresentable!" –Yaron Minsky
  115. 115. “A contact must have an email or a postal address” implies: • email address only, or • postal address only, or • both email address and postal address
  116. 116. type ContactInfo = | EmailOnly of EmailContactInfo | AddrOnly of PostalContactInfo | EmailAndAddr of EmailContactInfo * PostalContactInfo type Contact = { Name: Name ContactInfo : ContactInfo } “A contact must have an email or a postal address”
  117. 117. Composable types are almost as awesome as this
  118. 118. Communication is two-way. It's OK to push back
  119. 119. “A contact must have an email or a postal address” “A contact must have at least one way of being contacted”
  120. 120. “A contact must have at least one way of being contacted” type Contact = { Name: Name PrimaryContactInfo: ContactInfo SecondaryContactInfo: ContactInfo option } type ContactInfo = | Email of EmailContactInfo | Addr of PostalContactInfo
  121. 121. type ContactInfo = | Email of EmailContactInfo | Addr of PostalContactInfo | Facebook of FacebookInfo | SMS of PhoneNumber
  122. 122. Summary • Use code to represent the shared mental model and ubiquitous language • Designs will evolve. Embrace change. – Refactor towards deeper insight – Static types give you confidence to make changes • Use the power of a composable type system – Choices rather than inheritance – Options instead of null – Wrappers for constrained types – Make illegal states unrepresentable
  123. 123. If you liked this talk: • "Domain Modeling Made Functional" – fsharpforfunandprofit.com/ddd • More videos – fsharpforfunandprofit.com/video Thanks! Twitter: @ScottWlaschin
  • ibsd

    Sep. 11, 2020
  • OlegLustenko

    Jul. 30, 2020
  • LeandroAlexandrino2

    Jul. 22, 2020
  • RomeroMeireles1

    Jun. 2, 2020
  • AlexMonte7

    Jan. 17, 2020
  • SolveHeggem

    Nov. 16, 2019
  • maeh2k

    Nov. 5, 2019
  • ITSecMedia

    Oct. 18, 2019

(video at https://fsharpforfunandprofit.com/ddd/) Statically typed functional programming languages encourage a very different way of thinking about types. The type system is your friend, not an annoyance, and can be used in many ways that might not be familiar to OO programmers. Types can be used to represent the domain in a fine-grained, self documenting way. And in many cases, types can even be used to encode business rules so that you literally cannot create incorrect code. You can then use the static type checking almost as an instant unit test — making sure that your code is correct at compile time. In this talk, we'll look at some of the ways you can use types as part of a domain driven design process, with some simple real world examples in F#. No jargon, no maths, and no prior F# experience necessary.

Views

Total views

2,262

On Slideshare

0

From embeds

0

Number of embeds

82

Actions

Downloads

110

Shares

0

Comments

0

Likes

8

×