Slide 1

Slide 1 text

type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } // true if ownership of // email address is confirmed Domain Driven Design with the F# type system Prologue: how many things are wrong?

Slide 2

Slide 2 text

type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: which values are optional?

Slide 3

Slide 3 text

type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: what are the constraints?

Slide 4

Slide 4 text

type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: what groups are atomic?

Slide 5

Slide 5 text

type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } Prologue: domain logic?

Slide 6

Slide 6 text

Prologue: F# can help type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool }

Slide 7

Slide 7 text

Domain Driven Design with the F# type system Scott Wlaschin @ScottWlaschin fsharpforfunandprofit.com FPbridge.co.uk /ddd

Slide 8

Slide 8 text

What is DDD?

Slide 9

Slide 9 text

Functional Programming Domain Modelling

Slide 10

Slide 10 text

What I’m going talk about: • Demystifying functional programming • Functional programming for real world applications • F# vs. C# for domain driven design • Understanding the F# type system • Designing with types

Slide 11

Slide 11 text

Demystifying functional programming Why is it so hard?

Slide 12

Slide 12 text

Functional programming is scary

Slide 13

Slide 13 text

Functional programming is scary

Slide 14

Slide 14 text

Object oriented programming is scary

Slide 15

Slide 15 text

Functional programming is scary

Slide 16

Slide 16 text

Functional programming for real world applications

Slide 17

Slide 17 text

Functional programming is... ... good for mathematical and scientific tasks ... good for complicated algorithms ... really good for parallel processing ... but you need a PhD in computer science 

Slide 18

Slide 18 text

Functional programming is good for... Boring Line Of Business Applications (BLOBAs)

Slide 19

Slide 19 text

Must haves for BLOBA development... • Express requirements clearly • Rapid development cycle • High quality deliverables • Fun

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

F# vs. C# for Domain Driven Design A simple immutable object

Slide 22

Slide 22 text

How do you implement a Value object? Equality based on comparing all properties PersonalName: FirstName = "Alice" LastName = "Adams" PersonalName: FirstName = "Alice" LastName = "Adams" Equal  Therefore must be immutable

Slide 23

Slide 23 text

class PersonalName { public PersonalName(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstName { get; private set; } public string LastName { get; private set; } } Value object definition in C#

Slide 24

Slide 24 text

class PersonalName { public PersonalName(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstName { get; private set; } public string LastName { get; private set; } } Value object definition in C#

Slide 25

Slide 25 text

class PersonalName { // all the code from above, plus... public override int GetHashCode() { return this.FirstName.GetHashCode() + this.LastName.GetHashCode(); } public override bool Equals(object other) { return Equals(other as PersonalName); } public bool Equals(PersonalName other) { if ((object) other == null) { return false; } return FirstName == other.FirstName && LastName == other.LastName; } Value object definition in C# (extra code for equality)

Slide 26

Slide 26 text

type PersonalName = {FirstName:string; LastName:string} Value object definition in F#

Slide 27

Slide 27 text

This page intentionally left blank Value object definition in F# (extra code for equality)

Slide 28

Slide 28 text

How do you implement an Entity object? Equality based on some sort of id Person: Id = 1 Name = "Alice Adams" Person: Id = 1 Name = "Bilbo Baggins" Equal X  Generally has mutable content

Slide 29

Slide 29 text

class Person { public Person(int id, PersonalName name) { this.Id = id; this.Name = name; } public int Id { get; private set; } public PersonalName Name { get; set; } } Entity object definition in C# (part 1)

Slide 30

Slide 30 text

class Person { // all the code from above, plus... public override int GetHashCode() { return this.Id.GetHashCode(); } public override bool Equals(object other) { return Equals(other as Person); } public bool Equals(Person other) { if ((object) other == null) { return false; } return Id == other.Id; } } Entity object definition in C# (part 2)

Slide 31

Slide 31 text

[] type Person = {Id:int; Name:PersonalName} with override this.GetHashCode() = hash this.Id override this.Equals(other) = match other with | :? Person as p -> (this.Id = p.Id) | _ -> false Entity object definition in F# with equality override

Slide 32

Slide 32 text

Entity object definition in F# with no equality allowed [] type Person = {Id:int; Name:PersonalName} []

Slide 33

Slide 33 text

type Person = { ... ... ... } let tryCreatePerson name = // validate on construction // if input is valid return something // if input is not valid return error   Advantages of immutability

Slide 34

Slide 34 text

class PersonalName { public PersonalName(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstName { get; private set; } public string LastName { get; private set; } public override int GetHashCode() { return this.FirstName.GetHashCode() + this.LastName.GetHashCode(); } public override bool Equals(object other) { return Equals(other as PersonalName); } public bool Equals(PersonalName other) { if ((object) other == null) { return false; } return FirstName == other.FirstName && LastName == other.LastName; } } Reviewing the C# code so far... class Person { public Person(int id, PersonalName name) { this.Id = id; this.Name = name; } public int Id { get; private set; } public PersonalName Name { get; set; } public override int GetHashCode() { return this.Id.GetHashCode(); } public override bool Equals(object other) { return Equals(other as Person); } public bool Equals(Person other) { if ((object) other == null) { return false; } return Id == other.Id; } } : IValue : IEntity

Slide 35

Slide 35 text

[] type PersonalName = { FirstName : string; LastName : string } Reviewing the F# code so far... [] type Person = { Id : int; Name : PersonalName }

Slide 36

Slide 36 text

Comparing C# vs. F# C# F# Value objects? Non-trivial Easy Entity objects? Non-trivial Easy Value objects by default? No Yes Immutable objects by default? No Yes Can you tell Value objects from Entities at a glance? No Yes Understandable by non-programmer? No Yes C# vs. F# for DDD

Slide 37

Slide 37 text

F# for Domain Driven Design Communicating a domain model

Slide 38

Slide 38 text

Communication is hard... U-N-I-O-N-I-Z-E U-N-I-O-N-I-Z-E U-N-I-O-N-I-Z-E

Slide 39

Slide 39 text

Communication in DDD: “Bounded Context” Business Chemistry un-ionize unionize Supermarket Email System Spam Spam

Slide 40

Slide 40 text

Communication in DDD: “Bounded Context” Business Chemistry un-ionize unionize Supermarket Email System Spam Spam Sales Warehouse Product Product

Slide 41

Slide 41 text

Communication in DDD: “Bounded Context” Business Chemistry un-ionize unionize Supermarket Email System Spam Spam Sales Warehouse Product Product Marketing Finance Customer Customer

Slide 42

Slide 42 text

Communication in DDD: “Ubiquitous Language” Chemistry Ion Atom Molecule Polymer Compound Bond

Slide 43

Slide 43 text

Communication in DDD: “Ubiquitous Language” Chemistry Ion Atom Molecule Polymer Compound Bond Sales Product Promotion Customer Tracking

Slide 44

Slide 44 text

Communication in DDD: “Ubiquitous Language” Chemistry Ion Atom Molecule Polymer Compound Bond Sales Product Promotion Customer Tracking Warehouse Product Stock Transfer Depot Tracking

Slide 45

Slide 45 text

module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace 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 Ubiquitous language list type is built in

Slide 46

Slide 46 text

module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace 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

Slide 47

Slide 47 text

module CardGame = type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace 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

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

Understanding the F# type system An introduction to “algebraic” types An introduction to “algebraic” types

Slide 50

Slide 50 text

Composable types

Slide 51

Slide 51 text

Creating new types New types are constructed by combining other types using two basic operations: type typeW = typeX "times" typeY type typeZ = typeX "plus" typeY

Slide 52

Slide 52 text

Creating new types (a function) AddOne AddOne int –› int 1 2 3 4 2 3 4 5

Slide 53

Slide 53 text

Representing pairs AddPair ? –› int (1,2) (2,3) (3,4) (4,5) 3 5 7 9

Slide 54

Slide 54 text

Representing pairs × = (1,2) (2,3) (3,4) (4,5) 1 2 3 4 2 3 4 5

Slide 55

Slide 55 text

Representing pairs × = (true, false) (true, true) (false, false) (false, true) true false true false

Slide 56

Slide 56 text

Representing pairs pair of ints written int * int pair of bools written bool * bool

Slide 57

Slide 57 text

Using tuples for data × = Alice, Jan 12th Bob, Feb 2nd Carol, Mar 3rd Set of people Set of dates

Slide 58

Slide 58 text

Using tuples for data × = Alice, Jan 12th Bob, Feb 2nd Carol, Mar 3rd Set of people Set of dates type Birthday = Person * Date

Slide 59

Slide 59 text

Representing a choice Temp F IsFever ? –› bool true false Temp C or

Slide 60

Slide 60 text

Representing a choice + = 98˚ F 99˚ F 100˚ F 101˚ F 37.0˚ C 37.5˚ C 38.0˚ C 38.5˚ C Temp F Temp C or

Slide 61

Slide 61 text

Representing a choice + = 98˚ F 99˚ F 100˚ F 101˚ F 37.0˚ C 37.5˚ C 38.0˚ C 38.5˚ C Temp F Temp C or Tag these with “C” type Temp = | F of int | C of float

Slide 62

Slide 62 text

Using choices for data type PaymentMethod = | Cash | Cheque of int | Card of CardType * CardNumber

Slide 63

Slide 63 text

Working with a choice type type PaymentMethod = | Cash | Cheque of int | Card of CardType * CardNumber let printPayment method = match method with | Cash –› printfn “Paid in cash" | Cheque checkNo –› printfn “Paid by cheque: %i" checkNo | Card (cardType,cardNo) –› printfn “Paid with %A %A" cardType cardNo

Slide 64

Slide 64 text

Using choices vs. inheritance interface IPaymentMethod {..} class Cash : IPaymentMethod {..} class Cheque : IPaymentMethod {..} class Card : IPaymentMethod {..} type PaymentMethod = | Cash | Cheque of int | Card of CardType * CardNumber class Evil : IPaymentMethod {..} Data and code is scattered around many locations What goes in here? What is the common behaviour? OO version:

Slide 65

Slide 65 text

Summary: What are types for in FP? An annotation to a value for type checking type AddOne: int –› int Domain modelling tool type Deal = Deck –› (Deck * Card)

Slide 66

Slide 66 text

TYPE ALL THE THINGS

Slide 67

Slide 67 text

Designing with types What can we do with this type system?

Slide 68

Slide 68 text

Required vs. Optional type PersonalName = { FirstName: string; MiddleInitial: string; LastName: string; } required required optional

Slide 69

Slide 69 text

Null is not the same as “optional” Length string –› int “a” “b” “c” 1 2 3 “a” “b” “c” null

Slide 70

Slide 70 text

Spock, set phasers to null! That is illogical, Captain

Slide 71

Slide 71 text

Null is not the same as “optional” Length string –› int “a” “b” “c” null 1 2 3

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

Null is not allowed Length string –› int “a” “b” “c” null 1 2 3 X

Slide 74

Slide 74 text

A better way for optional values + = “a” “b” “c” “a” “b” “c” missing or Tag with “Nothing” type OptionalString = | SomeString of string | Nothing

Slide 75

Slide 75 text

type OptionalInt = | SomeInt of int | Nothing type OptionalString = | SomeString of string | Nothing type OptionalBool = | SomeBool of bool | Nothing Defining optional types

Slide 76

Slide 76 text

The built-in “Option” type type PersonalName = { FirstName: string MiddleInitial: string LastName: string } type Option<'T> = | Some of 'T | None

Slide 77

Slide 77 text

The built-in “Option” type type PersonalName = { FirstName: string MiddleInitial: Option LastName: string } type Option<'T> = | Some of 'T | None

Slide 78

Slide 78 text

The built-in “Option” type type PersonalName = { FirstName: string MiddleInitial: string option LastName: string } type Option<'T> = | Some of 'T | None

Slide 79

Slide 79 text

Single choice types type Something = | ChoiceA of A type Email = | Email of string type CustomerId = | CustomerId of int

Slide 80

Slide 80 text

Wrapping primitive types Is an EmailAddress just a string? Is a CustomerId just a int? Use single choice types to keep them distinct type EmailAddress = EmailAddress of string type PhoneNumber = PhoneNumber of string type CustomerId = CustomerId of int type OrderId = OrderId of int

Slide 81

Slide 81 text

Creating the EmailAddress type let createEmailAddress (s:string) = if Regex.IsMatch(s,@"^\S+@\S+\.\S+$") then (EmailAddress s) else ? let createEmailAddress (s:string) = if Regex.IsMatch(s,@"^\S+@\S+\.\S+$") then Some (EmailAddress s) else None createEmailAddress: string –› EmailAddress createEmailAddress: string –› EmailAddress option

Slide 82

Slide 82 text

Constrained strings type String50 = String50 of string let createString50 (s:string) = if s.Length <= 50 then Some (String50 s) else None createString50 : string –› String50 option

Slide 83

Slide 83 text

Constrained numbers + – 999999 Qty: Add To Cart

Slide 84

Slide 84 text

Constrained numbers type OrderLineQty = OrderLineQty of int let createOrderLineQty qty = if qty >0 && qty <= 99 then Some (OrderLineQty qty) else None createOrderLineQty: int –› OrderLineQty option

Slide 85

Slide 85 text

type Contact = { FirstName: string MiddleInitial: string LastName: string EmailAddress: string IsEmailVerified: bool } The challenge, revisited

Slide 86

Slide 86 text

The challenge, revisited type Contact = { FirstName: string MiddleInitial: string option LastName: string EmailAddress: string IsEmailVerified: bool }

Slide 87

Slide 87 text

The challenge, revisited type Contact = { FirstName: String50 MiddleInitial: String1 option LastName: String50 EmailAddress: EmailAddress IsEmailVerified: bool }

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

type Contact = { Name: PersonalName Email: EmailContactInfo } The challenge, revisited type PersonalName = { FirstName: String50 MiddleInitial: String1 option LastName: String50 } type EmailContactInfo = { EmailAddress: EmailAddress IsEmailVerified: bool }

Slide 90

Slide 90 text

Encoding domain logic 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 }

Slide 91

Slide 91 text

Encoding domain logic type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail type VerificationService = (EmailAddress * VerificationHash) –› VerifiedEmail option

Slide 92

Slide 92 text

type EmailAddress = ... type VerifiedEmail = VerifiedEmail of EmailAddress type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmail The challenge, completed type PersonalName = { FirstName: String50 MiddleInitial: String1 option LastName: String50 } type Contact = { Name: PersonalName Email: EmailContactInfo }

Slide 93

Slide 93 text

Making illegal states unrepresentable type Contact = { Name: Name Email: EmailContactInfo Address: PostalContactInfo }

Slide 94

Slide 94 text

Making illegal states unrepresentable type Contact = { Name: Name Email: EmailContactInfo Address: PostalContactInfo } New rule: “A contact must have an email or a postal address”

Slide 95

Slide 95 text

Making illegal states unrepresentable type Contact = { Name: Name Email: EmailContactInfo option Address: PostalContactInfo option } New rule: “A contact must have an email or a postal address”

Slide 96

Slide 96 text

Making illegal states unrepresentable “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

Slide 97

Slide 97 text

Making illegal states unrepresentable 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”

Slide 98

Slide 98 text

Making illegal states unrepresentable type Contact = { Name: Name Email: EmailContactInfo Address: PostalContactInfo } type Contact = { Name: Name ContactInfo : ContactInfo } type ContactInfo = | EmailOnly of EmailContactInfo | AddrOnly of PostalContactInfo | EmailAndAddr of EmailContactInfo * PostalContactInfo AFTER: Email and address merged into one type “A contact must have an email or a postal address” BEFORE: Email and address separate

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

Making illegal states unrepresentable type Contact = { Name: Name PrimaryContactInfo: ContactInfo SecondaryContactInfo: ContactInfo option } “A contact must have an email or a postal address” “A contact must have at least one way of being contacted” type ContactInfo = | Email of EmailContactInfo | Addr of PostalContactInfo

Slide 101

Slide 101 text

States and Transitions Modelling a common scenario

Slide 102

Slide 102 text

States and transitions State A State B State C Transition from A to B States and transitions Transition from B to A Transition from B to C

Slide 103

Slide 103 text

States and transitions Unverified EmailAddress Verified EmailAddress Verified States and transitions for email address Rule: "You can't send a verification message to a verified email" Rule: "You can't send a password reset message to a unverified email "

Slide 104

Slide 104 text

States and transitions Undelivered Out for delivery Delivered Put on truck Address not found Signed for States and transitions for shipments Rule: "You can't put a package on a truck if it is already out for delivery" Rule: "You can't sign for a package that is already delivered"

Slide 105

Slide 105 text

States and transitions Empty Cart Active Cart Paid Cart Add Item Remove Item Pay Add Item Remove Item States and transitions for shopping cart Rule: "You can't remove an item from an empty cart" Rule: "You can't change a paid cart" Rule: "You can't pay for a cart twice"

Slide 106

Slide 106 text

States and transitions Empty Cart Active Cart Paid Cart Add Item Remove Item Pay Add Item Remove Item States and transitions for shopping cart type ActiveCartData = { UnpaidItems: Item list } type PaidCartData = { PaidItems: Item list; Payment: Payment } no data needed

Slide 107

Slide 107 text

Modelling the shopping cart example type ActiveCartData = { UnpaidItems: Item list } type PaidCartData = { PaidItems: Item list; Payment: Payment} type ShoppingCart = | EmptyCart // no data | ActiveCart of ActiveCartData | PaidCart of PaidCartData Empty Cart Active Cart Paid Cart Add Item Remove Item Pay Add Item Remove Item

Slide 108

Slide 108 text

Shopping cart example initCart : Item –› ShoppingCart addToActive: (ActiveCartData * Item) –› ShoppingCart removeFromActive: (ActiveCartData * Item) –› ShoppingCart pay: (ActiveCartData * Payment) –› ShoppingCart Shopping Cart API Empty Cart Active Cart Paid Cart Add Item Remove Item Pay Add Item Remove Item

Slide 109

Slide 109 text

Shopping cart example let initCart item = { UnpaidItems=[item] } let addToActive (cart:ActiveCart) item = { cart with UnpaidItems = item :: cart.existingItems } Server code to add an item

Slide 110

Slide 110 text

Shopping cart example Client code to add an item using the API let addItem cart item = match cart with | EmptyCart –› initCart item | ActiveCart activeData –› addToActive(activeData,item) | PaidCart paidData –› ???

Slide 111

Slide 111 text

Shopping cart example let removeFromActive (cart:ActiveCart) item = let remainingItems = removeFromList cart.existingItems item match remainingItems with | [ ] –› EmptyCart | _ –› {cart with UnpaidItems = remainingItems} Server code to remove an item

Slide 112

Slide 112 text

Shopping cart example Client code to remove an item using the API let removeItem cart item = match cart with | EmptyCart –› ??? | ActiveCart activeData –› removeFromActive(activeData,item) | PaidCart paidData –› ???

Slide 113

Slide 113 text

Why design with state transitions? • Each state can have different allowable data. • All states are explicitly documented. • All transitions are explicitly documented. • It is a design tool that forces you to think about every possibility that could occur. Undelivered Out for delivery Delivered Put on truck Address not found Signed for

Slide 114

Slide 114 text

Review What I covered in this talk: • Ubiquitous language – Self-documenting designs • Algebraic types – products and sums • Designing with types – Options instead of null – Single case unions – Choices rather than inheritance – Making illegal states unrepresentable • States and transitions

Slide 115

Slide 115 text

Stuff I haven’t had time to cover: • Services • CQRS • The functional approach to use cases • Domain events • Error handling • And much more...

Slide 116

Slide 116 text

F# is low risk F# is the safe choice for functional-first development credit: @7sharp9 @MattDrivenDev Enterprise development F# on over 2 billion devices Mobile development Need to persuade your manager? -> FPbridge.co.uk/why-fsharp.html

Slide 117

Slide 117 text

Domain Driven Design with the F# type system DDD in F# resources fsharpforfunandprofit.com/ddd gorodinski.com tomasp.net/blog/type-first-development.aspx/ #fsharp on Twitter Contact me @ScottWlaschin FPbridge.co.uk