F#: Domain Driven Design (Decoupling)

Intro

In my last article, I discussed my attempt of practicing Domain Driven Design via F#. As proud as I was of my progress in learning F# in combination with Domain Driven Design, there were some refactoring opportunities that I wanted to take.

Decoupling Data from Behavior

In the last article, I had created two literals. One literal was for the target number of points that players would play up to. The other literal was the maximum number of allowed foul shots.

The literals were implemented as follows:

[<Literal>]
let Objective = 33

[<Literal>]
let MaxFoulShots = 3

I used these literals in the active pattern function:

let (|Underway|AlmostGame|Game|) (score,shot) =
 
    let shotValue = match shot with
                    | FoulShot                      -> 1
                    | TwoPointer   | TwoFoulShots   -> 2
                    | ThreePointer | ThreeFoulShots -> 3
 
    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (Objective - MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)
 
    | AlmostGame s,v -> if ( (s+v) < Objective )
                        then AlmostGame (s + v)
                        else Game Objective
 
    | GameTime   s,v -> Game s

Specifically, in this block here:

    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (Objective - MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)
 
    | AlmostGame s,v -> if ( (s+v) < Objective )
                        then AlmostGame (s + v)
                        else Game Objective
 
    | GameTime   s,v -> Game s

What I don’t like about the above function, is that I feel like the function depends on an external power in order to deliver a result. Hence, my function relies on the hardcoded data “33” and “3” for the total number of points (i.e. “Objective”) and the maximum foul shots allowed (i.e. 3) respectively. Again, I feel as if this data was just planted into my function inorganically.

To remedy this code smell, I decided to practice the art of decoupling data from behavior. Thus, I replaced the literals in my code with a record type called Rules. Thus, this record type would be leveraged as a parameter for my active pattern function. Hence, my active pattern function determines the stage of a game based on a shooter’s score.

The following record type was created:

Rules = {
 Target:int
 MaxFoulShots:int
}

The active pattern was modified to the following:

let (|Underway|AlmostGame|Game|) (score, shot, rules) =

    let shotValue = match shot with
                    | FoulShot                      -> 1
                    | TwoPointer   | TwoFoulShots   -> 2
                    | ThreePointer | ThreeFoulShots -> 3

    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (rules.Target - rules.MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)

    | AlmostGame s,v -> if ( (s+v) < rules.Target )
                        then AlmostGame (s + v)
                        else Game rules.Target

    | Game       s,v -> Game s

Pattern matching can leverage Active Patterns for enhanced readability. Thus, my function, makeShot, was updated to call the active pattern.

The makeShot function was the following:

let makeShot shot (shooter, defender) =

    match (shooter.Score, shot) with
    | Underway p   -> { shooter with Score=Underway p },   defender
    | AlmostGame p -> { shooter with Score=AlmostGame p }, defender
    | Game p       -> { shooter with Score=GameTime p },   defender

To incorporate the rules, I refactored makeShot to the following:

let makeShot shot (players, rules) =

    let shooter = players.Shooter
    let defender = players.Defender
    let game = shooter.Score, shot, rules

    match game with
    | Underway p   ->  { Shooter = { shooter with Score=Underway p }
                         Defender =  defender },
                         rules

    | AlmostGame p ->  { Shooter = { shooter with Score=AlmostGame p }
                         Defender =  defender },
                         rules

    | Game p       ->  { Shooter = { shooter with Score=Game p }
                         Defender =  defender },
                         rules

If you’ve noticed in the above code, I replaced the shooter and defender tuple with a record type called players. This decision was made because I found myself using this tuple of players in a number of places without any metadata about the order of the players within the tuple. That’s when I decided to create a type to define player roles.

The Players record type is the following:

type Players = {
      Shooter:PlayerScore
      Defender:PlayerScore
}

I then wrote a function to establish a change of role for the players:

let changePossession players = { Shooter=  players.Defender
                                 Defender= players.Shooter }

Refactoring the Client

Based on the alterations that I’ve made to the domain model, I had to refactor the client code in order for my program to compile.

The client for the game is the following:

// ************************************************************
(*Client*)
// ************************************************************
let players = startGame
let rules = { Target=33; MaxFoulShots=3 }

let firstPossession, _ = (players, rules) |> makeShot TwoPointer
let turnOver = firstPossession |> changePossession, rules

let final = turnOver |> makeShot ThreePointer
                     |> makeShot FoulShot

                     |> makeShot ThreePointer
                     |> makeShot TwoFoulShots

                     |> makeShot ThreePointer
                     |> makeShot ThreeFoulShots

                     |> makeShot ThreePointer
                     |> makeShot ThreePointer
                     |> makeShot ThreePointer
                     |> makeShot ThreePointer
                     |> makeShot ThreePointer

                     |> makeShot FoulShot
                     |> makeShot FoulShot
                     |> makeShot ThreeFoulShots

The entire domain model is this:

module Game
// ************************************************************
(*Types*)
// ************************************************************
type Player = | Player1 | Player2

type Players = {
      Shooter:PlayerScore
      Defender:PlayerScore
}

and PlayerScore = {
      Player:Player
      Score:Game
}

and Rules = {
     Target:int
     MaxFoulShots:int
}

and Shot =
    | TwoPointer| ThreePointer
    | FoulShot  | TwoFoulShots | ThreeFoulShots

and Game =
    | Underway of int
    | AlmostGame of int
    | Game of int

// ************************************************************
(*Functions*)
// ************************************************************
let (|Underway|AlmostGame|Game|) (score, shot, rules) =

    let shotValue = match shot with
                    | FoulShot                      -> 1
                    | TwoPointer   | TwoFoulShots   -> 2
                    | ThreePointer | ThreeFoulShots -> 3

    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (rules.Target - rules.MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)

    | AlmostGame s,v -> if ( (s+v) < rules.Target )
                        then AlmostGame (s + v)
                        else Game rules.Target

    | Game       s,v -> Game s

let makeShot shot (players, rules) =

    let shooter = players.Shooter
    let defender = players.Defender
    let game = shooter.Score, shot, rules

    match game with
    | Underway p   ->  { Shooter = { shooter with Score=Underway p }
                         Defender =  defender },
                         rules

    | AlmostGame p ->  { Shooter = { shooter with Score=AlmostGame p }
                         Defender =  defender },
                         rules

    | Game p       ->  { Shooter = { shooter with Score=Game p }
                         Defender =  defender },
                         rules

let startGame = { Shooter=  { Player=Player1; Score=Underway 0 }
                  Defender= { Player=Player2; Score=Underway 0 } }

let changePossession players = { Shooter=  players.Defender
                                 Defender= players.Shooter }

// ************************************************************
(*Client*)
// ************************************************************
let players = startGame
let rules = { Target=33; MaxFoulShots=3 }

let firstPossession, _ = (players, rules) |> makeShot TwoPointer
let turnOver = firstPossession |> changePossession, rules

let final = turnOver |> makeShot ThreePointer
                     |> makeShot FoulShot

                     |> makeShot ThreePointer
                     |> makeShot TwoFoulShots

                     |> makeShot ThreePointer
                     |> makeShot ThreeFoulShots

                     |> makeShot ThreePointer
                     |> makeShot ThreePointer
                     |> makeShot ThreePointer
                     |> makeShot ThreePointer
                     |> makeShot ThreePointer

                     |> makeShot FoulShot
                     |> makeShot FoulShot
                     |> makeShot ThreeFoulShots
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: