(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.
11. • 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?
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
Sharedlanguage What DDD source code should look like
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
* means a pair. Choose one from each type
list type is built in
20. 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
21. 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
22. 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?
30. 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
31. 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
32. In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
33. 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
34. In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
PlayerManager
DeckBase
AbstractCardProxyFactoryBean
35. 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
36. 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
47. 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
48. 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
49. 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
50. 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
51. 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
52. 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
56. New types are built from smaller types by:
Composing with “AND”
Composing with “OR”
57. Example: pairs, tuples, records
FruitSalad = One each of and and
Compose with “AND”
type FruitSalad = {
Apple: AppleVariety
Banana: BananaVariety
Cherry: CherryVariety
}
58. Snack = or or
Compose with “OR”
type Snack =
| Apple of AppleVariety
| Banana of BananaVariety
| Cherry of CherryVariety
60. 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
61. 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:
62. type CheckNumber = int
type CardNumber = string
In FP you would probably implement by composing
types, like this:
63. type CheckNumber = ...
type CardNumber = …
type CardType = Visa | Mastercard
type CreditCardInfo = {
CardType : CardType
CardNumber : CardNumber
}
64. type CheckNumber = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| Check of CheckNumber
| Card of CreditCardInfo
65. type CheckNumber = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| Check of CheckNumber
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
66. 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 }
67. 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 }
68. What are types for?
An annotation to a value for type checking
type AddOne: int –› int
Domain modelling tool
type Deal = Deck –› (Deck * Card)
78. type OptionalInt =
| SomeInt of int
| Nothing
type OptionalString =
| SomeString of string
| Nothing
type OptionalBool =
| SomeBool of bool
| Nothing
79. type PersonalName = {
FirstName: string
MiddleInitial: string
LastName: string
}
type Option<'T> =
| Some of 'T
| None
80. type PersonalName = {
FirstName: string
MiddleInitial: Option<string>
LastName: string
}
type Option<'T> =
| Some of 'T
| None
81. type PersonalName = {
FirstName: string
MiddleInitial: string option
LastName: string
}
type Option<'T> =
| Some of 'T
| None
83. Modeling simple values
• Avoid "Primitive Obsession"
– Simple values should not be modelled with
primitive types
– "Does 'float' have something to do with water?"
84. 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
85. 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
86. type EmailAddress = EmailAddress of string
type PhoneNumber = PhoneNumber of string
type CustomerId = CustomerId of int
type OrderId = OrderId of int
88. let createEmailAddress (s:string) =
if s.Contains("@")
then Some (EmailAddress s)
else None
createEmailAddress:
string –› EmailAddress option
89. type String50 = String50 of string
let createString50 (s:string) =
if s.Length <= 50
then Some (String50 s)
else None
createString50 :
string –› String50 option
91. type OrderLineQty = OrderLineQty of int
let createOrderLineQty qty =
if qty > 0 && qty <= 99
then Some (OrderLineQty qty)
else None
createOrderLineQty:
int –› OrderLineQty option
100. • 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 }
101. type VerifiedEmail =
VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
102. type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
103. type VerifiedEmail =
VerifiedEmail of EmailAddress
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
104. 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!
115. type Contact = {
Name: Name
Email: EmailContactInfo
Address: PostalContactInfo
}
116. type Contact = {
Name: Name
Email: EmailContactInfo
Address: PostalContactInfo
}
New rule:
“A contact must have an email or a postal address”
117. type Contact = {
Name: Name
Email: EmailContactInfo option
Address: PostalContactInfo option
}
New rule:
“A contact must have an email or a postal address”
119. “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
120. 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”
123. “A contact must have an email or a postal address”
“A contact must have at least one way of being contacted”
124. “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
125. type ContactInfo =
| Email of EmailContactInfo
| Addr of PostalContactInfo
| Facebook of FacebookInfo
| SMS of PhoneNumber
126. 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
127. If you liked this talk:
• "Domain Modeling Made Functional"
– fsharpforfunandprofit.com/ddd
• More videos
– fsharpforfunandprofit.com/video
Thanks!
Twitter: @ScottWlaschin