Domain Modeling Made Functional

YouTube

  • type Contact in our design
  • What is optional (MiddleInitial)
  • What are the constraints? (string can't be more than 50 chars)
  • What is the domain logic?

Domain Driven Design

Care about the domain.

  • The goal is comunication.

Communication

  • Different contexts same word. How can we be clear in communication?

Card Game example!

module CardGame =
	type Suit = Club | Diamond | Spade | Heart

	type Card = Suit * Rank // pair

	type Hand = Card list

	type Player = {
		Name: string;
		Hand: Hand;
	}

	type Deal = Deck -> (Deck * Card) // deal is an action where you takes a deck and get a card and the rest of the deck
	type PickupCard = (Hand * Carol) -> Hand

F# Type System

  • Composable Type System

  • int -> int

  • Pair Type
  • type Birthday = Person * Data

  • Choice type

  • Really useful for domain modeling

  • Use Pattern matching!

  • It's a domain modeling unit test!!!

Optional Values

  • Choice of Some Value or None Value

  • Generic Type!

  • This keeps them distinct!

Constraiened Values

This is really powerful.

  • Constained String

ConstrainedTypeExamples.fsx Gist

module ConstrainedTypes =
    open System

    // ---------------------------------------------
    // Constrained String50 (FP-style)
    // ---------------------------------------------

    /// Type with constraint that value must be non-null
    /// and <= 50 chars.
    type String50 = private String50 of string

    /// Module containing functions related to String50 type
    module String50 =

        // NOTE: these functions can access the internals of the
        // type because they are in the same scope (namespace/module)
        
        /// constructor
        let create str = 
            if String.IsNullOrEmpty(str) then
                None
            elif String.length str > 50 then
                None
            else
                Some (String50 str)

        // function used to extract data since type is private
        let value (String50 str) = str

module Client = 
    open ConstrainedTypes 


    // ---------------------------------------------
    // Constrained String50 (FP-style)
    // ---------------------------------------------

    let s50Bad = String50 "abc" 
    // ERROR: The union cases or fields of the type 'String50' are not accessible from this code location
    
    let s50opt = String50.create "abc" 
    s50opt 
    |> Option.map String50.value 
    |> Option.map (fun s -> s.ToUpper())
    |> Option.iter (printfn "%s")

  • Type Validated

  • Create a new type

  • Well Modeled in the Domain! Less Unit Tests
type EmailAddress = ...

type VerifiedEmail = VerifiedEmail of EmailAddress

// ?
type EmailContactInfo = 
| Unverified of EmailAddress
| Verified of VerifiedEmail

type PersonName = {
	FirstName: String50
	MiddleInitial: String1 option
	LastName: String50
}

type Contact = {
	Name: PersonalName
	Email: EmailContactInfo
}

Make Illegal State Unrepresentable

  • Use a choice type
  • Self-Documenting Code

  • They really wanted to have one way of being contacted.
  • You could have two email addresses but this is what the business was looking for so it's ok.

Review

Link