class Contact {

    string Firstname;
    string MiddleInitial;
    string LastName;

 

    string EmailAdress;
    bool IsEmailVerified;
}

How many things are

wrong with this design? 

class Contact {

    string Firstname;
    string MiddleInitial;
    string LastName;

 

    string EmailAdress;
    bool IsEmailVerified;
}

Which values are optional?

What are the constraints?

Which fields are linked?

What is the domain logic?

Your type system may be able to help with these questions!

Domain Driven Design

 

with your Typesystem

»Focus on the domain and

   domain logic rather than

   technology«

- Eric Evans

Value objects

public class PersonalName {

    private String firstName;
    private String lastName;

 

    public PersonalName(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

 

    public String getLastName() {
        return lastName;
    }

 

    public String getFirstName() {
        return firstName;
    }
}

In Java or C#

public class PersonalName {

   // All the code from above, plus...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        PersonalName that = (PersonalName) o;

        if (!firstName.equals(that.firstName)) return false;
        if (!lastName.equals(that.lastName)) return false;

        return true;
    }

 

    @Override
    public int hashCode() {
        int result = firstName.hashCode();
        result = 31 * result + lastName.hashCode();
        return result;
    }

}

In Java or C#

is this a reasonable amount of code?

case class PersonalName(

         firstName : String,

         lastName : String

)

In Scala

type PersonalName = {

         firstName : string,

         lastName : string

}

In F#

this page was intentionally left blank

Entities

public class Person {
    private int id;
    private PersonalName personalName;

 

    public Person(int id, PersonalName personalName) {
        this.id = id;
        this.personalName = personalName;
    }

    public int getId() {
        return id;
    }

    public PersonalName getPersonalName() {
        return personalName;
    }

    public void setPersonalName(PersonalName personalName) {
        this.personalName = personalName;
    }

}

In Java or C#

public class PersonalName {

   // All the code from above, plus...

   

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (id != person.id) return false;

        return true;
    }

 

    @Override
    public int hashCode() {
        return id;
    }

}

In Java or C#

Now compares on Id

case class PersonalName(id : Int)(

         val firstName : String,

         val lastName : String

)

In Scala

[<CustomEquality; NoComparison>]

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

In F#

[<NoEquality; NoComparison>]

type Person = { Id : int; Name : PersonalName }

In F#

Scala & F# for Domain Driven Design

Communicating a Domain Model

Communication is hard!

U-N-I-O-N-I-Z-E

Supermarket

Spam

Email system

Spam

Bounded context

Sales

Product

Warehouse

Product

Bounded context

Marketing

Customer

Finance

Customer

Bounded context

Chemistry

Ubiquitous Language

Ion    Atom    Molecule

Polymer    Compound

Bond

Sales

Ubiquitous Language

Product    Promotion

Customer    Tracking

Warehouse

Ubiquitous Language

Product    Stock    Transfer

Depot    Tracking

Understanding algebraic types

composable types

Product types

Alice, Jan 12th

Bob, Feb 2nd

=

Set of

People

Set of

Dates

x

type Birthday = Person * Date

Sum types

=

Set of

Integers

Set of

Floats

+

abstract class Temp

case class F(val : int) extends Temp

case class C(val : Float) extends Temp

Temp

Fahrenheit

Temp

Celcius

or

Using choices for data

type PaymentMethod =

  | Cash

  | Cheque of int

  | Card of CardType * CardNumber

 

Working with a choice type

let printPayment = function

  | Cash -> printfn "Paid in cash"

  | Cheque checkNo -> printfn "Paid by cheque: %i" checkNo

  | Card (type, num) -> printfn "Paid with %A %A" cardType cardNo

What are types for in

Functional Programming

An annotation to a value for type checking

type AddOne = Int => Int

Domain modeling tool!

type Deal = Deck => (Deck, Card)

a good static type system is like having compile type tests

Designing with types

What can we do with the type system?

case class PersonalName(

         firstName : String,

         middleInitial : String,

         lastName : String

)

Required vs Optional

required

required

optional

How can we represent optional values?

"a"

"b"

"c"

null

Length

String => Int

1

2

3

Null is not the same as "optional"

null is the Saruman of static typing!

A better way to represent nothing

=

+

Tag these with

"SomeString"

"a"

"b"

"c"

Missing

or

"a"

"b"

"c"

Tag with

"Nothing"

type OptionalString = SomeString of String | Nothing

The built in option type

abstract class Option[A]

case class Some[A](a : A) extends Option[A]

case object None extends Option[Nothing]

type Option<'T> =

  | Some of 'T

  | None

case class PersonalName(

  firstName : String,

  middleInitial : Option[String]

  lastName : String

)

Single choice types

case class EmailAddress(email : String)

type CustomerId = CustomerId of Int

case class PhoneNumber(number : String)

distinct types!

type OrderId = OrderId of int

Creating an EmailAddress instance

let createEmailAddress (s : string) =

  if Regex.Match(s, @"^\S+@\S+\.\S+$")

    then Some(EmailAddress s)

    else None

createEmailAddress:

  string -> Option<EmailAddress>

Constrained strings

let createString50 (s : string) =

  if s.Length <= 50

    then Some(String50 s)

    else None

createString50:

  string -> Option<String50>

The challenge, revised

case class Contact(

  name : PersonalName,

  email : EmailContactInfo

)

case class PersonalName(

  firstName : String50,

  middleInitial : Option[String1],

  lastName : String50

)

case class EmailContactInfo(

  emailAddress : EmailAddress,

  isEmailVerified : boolean

)

Encoding domain logic

case class EmailContactInfo(

  emailAddress : EmailAddress,

  isEmailVerified : boolean

)

anyone can set this to true

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

Encoding domain logic

type VerifiedEmail = VerifiedEmail of EmailAddress

type EmailContactInfo =

  | Unverified of EmailAddress

  | Verified of VerifiedEmail

type VerificationService =

  (EmailAddress * VerificationHash) -> Option<VerifiedEmail>

 

The challenge completed!

type VerifiedEmail = VerifiedEmail of EmailAddress

type EmailContactInfo =

  | Unverified of EmailAddress

  | Verified of VerifiedEmail

type Contact = {

  Name : PersonalName,

  Email : EmailContactInfo

}

type PersonalName = {

  FirstName : String50,

  MiddleInitial : Option<String1>,

  LastName : String50

}

type EmailAddress = EmailAddress of string

The ubiquitous language is

evolving along with the design

Making illegal states unrepresentable

type Contact = {

  Name : PersonalName,

  Email : EmailContactInfo,

  Address : PostalContactInfo

}

added some time later

Making illegal states unrepresentable

type Contact = {

  Name : PersonalName,

  Email : EmailContactInfo,

  Address : PostalContactInfo

}

Doesn't meet new requirements

New rule: "A contact must have an email or a postal address"

Making illegal states unrepresentable

type Contact = {

  Name : PersonalName,

  Email : Option<EmailContactInfo>,

  Address : Option<PostalContactInfo>

}

Doesn't meet new requirements either!

New rule: "A contact must have an email or a postal address"

»Make illegal states unrepresentable«

- Yaron Minsky

Making illegal states unrepresentable

implies:

  • email address only
  • postal address only
  • both email and postal address

only three possibilities

New rule: "A contact must have an email or a postal address"

Making illegal states unrepresentable

type ContactInfo =

  | EmailOnly of EmailContactInfo

  | AddrOnly of PostalContactInfo

  | EmailAndAdrr of EmailContactInfo * PostalContactInfo

only three possibilities

New rule: "A contact must have an email or a postal address"

requirements are now encoded in the types!

type Contact = {

  Name : PersonalName

  ContactInfo : ContactInfo

}

Static types are

almost as

awesome as

this!

Making illegal states unrepresentable

type ContactInfo =

  | Email of EmailContactInfo

  | Postal of PostalContactInfo

one way of being contacted is required

New rule: "A contact must have an email or a postal address"

Is this really what the business wants?

type Contact = {

  Name : Name

  PrimaryContactInfo : ContactInfo

  SecondaryContactInfo : Option<ContactInfo>

}

States and Transitions

Modelling a common scenario

State A

State B

State C

Transition from A to B

Transition from B to A

Transition from B to C

Unverified EmailAddress

Verified

Verified EmailAddress

Rule: You can't send a verification message to a verified email

Rule: You can't send a password reset message to an unverified email

States and transitions for email address

Empty

Active

Paid

Add Item

Remove Item

Pay

States and transitions for shopping carts

Add Item

Remove Item

Rule: You can't remove an item from an empty cart

Rule: You can't charge a paid cart

Rule: You can't pay for a cart twice

Empty

Active

Paid

Add Item

Remove Item

Pay

States and transitions for shopping carts

Add Item

Remove Item

no data needed

type ActiveCartData = {

  UnpaidItems : Item list

}

type PaidCartData = {

  PaidItems : Item list

  Payment : Payment

}

What data do we need to store?

Modelling the shopping cart

type ActiveCartData = {

  UnpaidItems : Item list

}

type PaidCartData = {

  PaidItems : Item list

  Payment : Payment

}

type ShoppingCart = 

| EmptyCart

| ActiveCart of ActiveCartData

| PaidCart of PaidCartData

one of three states

Modelling the shopping cart: Server API

initCart:

  Item -> ShoppingCart

 

addToActive:

  (ActiveCartData * Item) -> ShoppingCart

 

removeFromActive:

  (ActiveCartData * Item) -> ShoppingCart

 

pay:

  (ActiveCartData * Payment) -> ShoppingCart

might be empty or active

Modelling the shopping cart: Server API

let initCart item =

  { UnpaidItems = [item] }

 

 

 

let addToActive (cart : ActiveCart) item =

  { cart with UnpaidItems = item :: cart.existingItems }

create a new active cart

prepend item to list

Modelling the shopping cart: Client API

let addItem cart item =

  match cart with

    | EmptyCart ->

         initCart item

    | ActiveCart activeData ->

         addToActive(activeData, item)

    | PaidCart paidData ->

         ???

cannot accidentally alter a paid cart!

Modelling the shopping cart: Server API

let removeFromActive (cart : ActiveCart) item =

  let remaining = removeFromList cart.existingItems item

  match remaining with

    | [] ->

         EmptyCart

    | _ ->

         { cart with UnpaidItems = remainingItems }

create a new ActiveCart with

the item remove

Modelling the shopping cart: Client API

let removeItem cart item =

  match cart with

    | EmptyCart->

         ???

    | ActiveCart activeData ->

        removeFromActive(activeData, item)

    | PaidCart paidData ->

        ???

compiler won't let you remove from an empty cart!

Why design with state transitions?

  • Each state can have different allowable data
  • All states are explicitly documented
  • All transitions are explicitly documented
  • It's a design tool that forces you to think about every possible thing that can occur

Undelivered

Out for delivery

Delivered

Put on truck

Address not found

Signed for

How can you get your hands

on that awesome power?

options

  • Change your language to either Scala, F#, OCaml or Haskell
  • Do it in your current language but live with the pain
    • Nulls are often everywhere in existing APIs 
    • Abundances of sum-like type hierarchies and Value-types in Java is very tiresome and verbose
  • Use a subset of the features in your current language

Case study: Java8

Java 8 introduces java.util.Optional<T>

Includes a number of higher-order-functions to work with optional values: map, flatmap, filter, ifPresent, orElseGet

String version = "UNKNOWN";
if(computer != null){
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null){
    USB usb = soundcard.getUSB();
    if(usb != null){
      version = usb.getVersion();
    }
  }
}

unsafe!

String version = computer.getSoundcard().getUSB().getVersion();
public class Computer {
  private Optional<Soundcard> soundcard;  
  public Optional<Soundcard> getSoundcard() { ... }
  ...
}

public class Soundcard {
  private Optional<USB> usb;
  public Optional<USB> getUSB() { ... }

}

public class USB{
  public String getVersion(){ ... }
}
String soundcardUsbName = 
  computer.flatMap(Computer::getSoundcard)
          .flatMap(Soundcard::getUSB)
          .map(USB::getVersion)
          .orElse("UNKNOWN");

Cool links pointing to hot stuff

DDDing with Types

By Simon Stender Boisen

DDDing with Types

  • 949